linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 01/11] misc: inv_mpu primary header file and README file.
@ 2011-07-01  2:18 Nathan Royer
  2011-07-01  2:18 ` [PATCH 02/11] misc: mpu3050 Register definition and Private data Nathan Royer
                   ` (14 more replies)
  0 siblings, 15 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

This files defines the userspace interface and how to integrate it into a
platform.

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---

This is the first of many patch files for the inv_mpu driver in its current
state.  This is our first time submitting this driver, so I expect there to be
a lot wrong with it, and expect to need to fix many things.

The inv_mpu driver attepts to implement a Motion Processing Unit interface.  As
a unit, an accelerometer, magnetometer, gyroscope, and/or altimiter data is
fused together to produce calibrated data and a quaternion.

The inv_mpu driver interface is currently implemented as a misc device, but may
need to change to include both sysfs attributes and input devices.  I think
that we will continue to need the ioctl interface, but many of the ioctls can
be replace by attributes and/or input devices.

The mpu3050 has an i2c master interface designed to control an accelerometer
and a Digital Motion Processor (DMP) used to perform sensor fusion on the
gyroscope and accelerometer.  This data is then read out of the mpu3050 fifo
and sent to userspace for distribution and optional propritary processing, such
as fusion with a compass to produce a 9 axis quaternion.

Some question I have at the start are:
1) Is there a master design or standard interface for Motion Processing
devices, specifically ones that do calibration, sensor fusion, and or have a
micro-controller to do some of this work.
2) Is there a standard way to integrate user space components with kernel side
components.
3) Should data be pushed back to the driver from userspace, and made available
as an input device or should it remain as a character device.
4) Can a 4 element quaternion be added to input.h:
ABS_QUATERNION_1 ABS_QUATERNION_I ABS_QUATERNION_J ABS_QUATERNION_K
for <1, i, j, k>
5) Should we instead use a rotation vector as defined in the Android sensor:
http://developer.android.com/reference/android/hardware/SensorEvent.html
6) Are there any other major design concerns?
7) Can an input device also have a character device interface for proprietary
customization.

 drivers/misc/inv_mpu/README |  104 ++++++++++++
 include/linux/mpu.h         |  365 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 469 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/README
 create mode 100644 include/linux/mpu.h

diff --git a/drivers/misc/inv_mpu/README b/drivers/misc/inv_mpu/README
new file mode 100644
index 0000000..ce592c8
--- /dev/null
+++ b/drivers/misc/inv_mpu/README
@@ -0,0 +1,104 @@
+Kernel driver mpu
+=====================
+
+Supported chips:
+  * InvenSense IMU3050
+    Prefix: 'mpu3050'
+    Datasheet:
+        PS-MPU-3000A-00.2.4b.pdf
+
+Author: InvenSense <http://invensense.com>
+
+Description
+-----------
+The mpu is a motion processor unit that controls the mpu3050 gyroscope, a slave
+accelerometer, a compass and a pressure sensor.  This document describes how to
+install the driver into a Linux kernel.
+
+Sysfs entries
+-------------
+/dev/mpu
+/dev/mpuirq
+/dev/accelirq
+/dev/compassirq
+/dev/pressureirq
+
+General Remarks MPU3050
+-----------------------
+* Valid addresses for the MPU3050 is 0x68.
+* Accelerometer must be on the secondary I2C bus for MPU3050, the
+  magnetometer must be on the primary bus and pressure sensor must
+  be on the primary bus.
+
+Programming the chip using /dev/mpu
+----------------------------------
+Programming of 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 mpu.h.  Typically this is done by the mllite library in user
+space.
+
+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 mpu_platform_data structure must
+be created and populated and set in the i2c_board_info_structure.  For details
+of each structure member see mpu.h.  All values below are simply an example and
+should be modified for your platform.
+
+#include <linux/mpu.h>
+
+static struct mpu_platform_data mpu3050_data = {
+	.int_config  = 0x10,
+	.orientation = {  -1,  0,  0,
+			   0,  1,  0,
+			   0,  0, -1 },
+};
+
+/* accel */
+static struct ext_slave_platform_data inv_mpu_kxtf9_data = {
+	.bus         = EXT_SLAVE_BUS_SECONDARY,
+	.orientation = {  -1,  0,  0,
+			  0,  1,  0,
+			  0,  0, -1 },
+};
+/* compass */
+static struct ext_slave_platform_data inv_mpu_ak8975_data = {
+	.bus         = EXT_SLAVE_BUS_PRIMARY,
+	.orientation = { 1, 0, 0,
+			 0, 1, 0,
+			 0, 0, 1 },
+};
+
+static struct i2c_board_info __initdata panda_inv_mpu_i2c4_boardinfo[] = {
+	{
+		I2C_BOARD_INFO("mpu3050", 0x68),
+		.irq = (IH_GPIO_BASE + MPUIRQ_GPIO),
+		.platform_data = &mpu3050_data,
+	},
+	{
+		I2C_BOARD_INFO("kxtf9", 0x0F),
+		.irq = (IH_GPIO_BASE + ACCEL_IRQ_GPIO),
+		.platform_data = &inv_mpu_kxtf9_data
+	},
+	{
+		I2C_BOARD_INFO("ak8975", 0x0E),
+		.irq = (IH_GPIO_BASE + COMPASS_IRQ_GPIO),
+		.platform_data = &inv_mpu_ak8975_data,
+	},
+};
+
+Typically the IRQ is a GPIO input pin and needs to be configured properly.  If
+in the above example GPIO 168 corresponds to IRQ 299, the following should be
+done as well:
+
+#define MPU_GPIO_IRQ 168
+
+    gpio_request(MPU_GPIO_IRQ,"MPUIRQ");
+    gpio_direction_input(MPU_GPIO_IRQ)
+
+Dynamic Debug
+=============
+
+The mpu3050 makes use of dynamic debug.  For details on how to use this
+refer to Documentation/dynamic-debug-howto.txt
diff --git a/include/linux/mpu.h b/include/linux/mpu.h
new file mode 100644
index 0000000..18d2da0
--- /dev/null
+++ b/include/linux/mpu.h
@@ -0,0 +1,365 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+#ifndef __MPU_H_
+#define __MPU_H_
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+/* Number of axes on each sensor */
+#define GYRO_NUM_AXES               (3)
+#define ACCEL_NUM_AXES              (3)
+#define COMPASS_NUM_AXES            (3)
+
+struct mpu_read_write {
+	/* Memory address or register address depending on ioctl */
+	__u16 address;
+	__u16 length;
+	__u8 *data;
+};
+
+enum mpuirq_data_type {
+	MPUIRQ_DATA_TYPE_MPU_IRQ,
+	MPUIRQ_DATA_TYPE_SLAVE_IRQ,
+	MPUIRQ_DATA_TYPE_PM_EVENT,
+	MPUIRQ_DATA_TYPE_NUM_TYPES,
+};
+
+/* User space PM event notification */
+#define MPU_PM_EVENT_SUSPEND_PREPARE (3)
+#define MPU_PM_EVENT_POST_SUSPEND    (4)
+
+struct mpuirq_data {
+	__u32 interruptcount;
+	__u64 irqtime;
+	__u32 data_type;
+	__s32 data;
+};
+
+enum ext_slave_config_key {
+	MPU_SLAVE_CONFIG_ODR_SUSPEND,
+	MPU_SLAVE_CONFIG_ODR_RESUME,
+	MPU_SLAVE_CONFIG_FSR_SUSPEND,
+	MPU_SLAVE_CONFIG_FSR_RESUME,
+	MPU_SLAVE_CONFIG_MOT_THS,
+	MPU_SLAVE_CONFIG_NMOT_THS,
+	MPU_SLAVE_CONFIG_MOT_DUR,
+	MPU_SLAVE_CONFIG_NMOT_DUR,
+	MPU_SLAVE_CONFIG_IRQ_SUSPEND,
+	MPU_SLAVE_CONFIG_IRQ_RESUME,
+	MPU_SLAVE_WRITE_REGISTERS,
+	MPU_SLAVE_READ_REGISTERS,
+	MPU_SLAVE_CONFIG_INTERNAL_REFERENCE,
+	/* AMI 306 specific config keys */
+	MPU_SLAVE_PARAM,
+	MPU_SLAVE_WINDOW,
+	MPU_SLAVE_READWINPARAMS,
+	MPU_SLAVE_SEARCHOFFSET,
+	/* AKM specific config keys */
+	MPU_SLAVE_READ_SCALE,
+	/* MPU3050 and MPU6050 Keys */
+	MPU_SLAVE_INT_CONFIG,
+	MPU_SLAVE_EXT_SYNC,
+	MPU_SLAVE_FULL_SCALE,
+	MPU_SLAVE_LPF,
+	MPU_SLAVE_CLK_SRC,
+	MPU_SLAVE_DIVIDER,
+	MPU_SLAVE_DMP_ENABLE,
+	MPU_SLAVE_FIFO_ENABLE,
+	MPU_SLAVE_DMP_CFG1,
+	MPU_SLAVE_DMP_CFG2,
+	MPU_SLAVE_TC,
+	MPU_SLAVE_GYRO,
+	MPU_SLAVE_ADDR,
+	MPU_SLAVE_PRODUCT_REVISION,
+	MPU_SLAVE_SILICON_REVISION,
+	MPU_SLAVE_PRODUCT_ID,
+	MPU_SLAVE_GYRO_SENS_TRIM,
+	MPU_SLAVE_ACCEL_SENS_TRIM,
+	MPU_SLAVE_RAM,
+	/* -------------------------- */
+	MPU_SLAVE_CONFIG_NUM_CONFIG_KEYS
+};
+
+/* For the MPU_SLAVE_CONFIG_IRQ_SUSPEND and MPU_SLAVE_CONFIG_IRQ_RESUME */
+enum ext_slave_config_irq_type {
+	MPU_SLAVE_IRQ_TYPE_NONE,
+	MPU_SLAVE_IRQ_TYPE_MOTION,
+	MPU_SLAVE_IRQ_TYPE_DATA_READY,
+};
+
+/* Structure for the following IOCTS's
+ * MPU_CONFIG_GYRO
+ * MPU_CONFIG_ACCEL
+ * MPU_CONFIG_COMPASS
+ * MPU_CONFIG_PRESSURE
+ * MPU_GET_CONFIG_GYRO
+ * MPU_GET_CONFIG_ACCEL
+ * MPU_GET_CONFIG_COMPASS
+ * MPU_GET_CONFIG_PRESSURE
+ *
+ * @key one of enum ext_slave_config_key
+ * @len length of data pointed to by data
+ * @apply zero if communication with the chip is not necessary, false otherwise
+ *        This flag can be used to select cached data or to refresh cashed data
+ *        cache data to be pushed later or push immediately.  If true and the
+ *        slave is on the secondary bus the MPU will first enger bypass mode
+ *        before calling the slaves .config or .get_config funcion
+ * @data pointer to the data to confgure or get
+ */
+struct ext_slave_config {
+	__u8 key;
+	__u16 len;
+	__u8 apply;
+	void *data;
+};
+
+enum ext_slave_type {
+	EXT_SLAVE_TYPE_GYROSCOPE,
+	EXT_SLAVE_TYPE_ACCEL,
+	EXT_SLAVE_TYPE_COMPASS,
+	EXT_SLAVE_TYPE_PRESSURE,
+	/*EXT_SLAVE_TYPE_TEMPERATURE */
+
+	EXT_SLAVE_NUM_TYPES
+};
+
+enum ext_slave_id {
+	ID_INVALID = 0,
+
+	ACCEL_ID_LIS331,
+	ACCEL_ID_LSM303A,
+	ACCEL_ID_LIS3DH,
+	ACCEL_ID_KXSD9,
+	ACCEL_ID_KXTF9,
+	ACCEL_ID_BMA150,
+	ACCEL_ID_BMA222,
+	ACCEL_ID_BMA250,
+	ACCEL_ID_ADXL34X,
+	ACCEL_ID_MMA8450,
+	ACCEL_ID_MMA845X,
+	ACCEL_ID_MPU6050,
+
+	COMPASS_ID_AK8975,
+	COMPASS_ID_AK8972,
+	COMPASS_ID_AMI30X,
+	COMPASS_ID_AMI306,
+	COMPASS_ID_YAS529,
+	COMPASS_ID_YAS530,
+	COMPASS_ID_HMC5883,
+	COMPASS_ID_LSM303M,
+	COMPASS_ID_MMC314X,
+	COMPASS_ID_HSCDTD002B,
+	COMPASS_ID_HSCDTD004A,
+
+	PRESSURE_ID_BMA085,
+};
+
+enum ext_slave_endian {
+	EXT_SLAVE_BIG_ENDIAN,
+	EXT_SLAVE_LITTLE_ENDIAN,
+	EXT_SLAVE_FS8_BIG_ENDIAN,
+	EXT_SLAVE_FS16_BIG_ENDIAN,
+};
+
+enum ext_slave_bus {
+	EXT_SLAVE_BUS_INVALID = -1,
+	EXT_SLAVE_BUS_PRIMARY = 0,
+	EXT_SLAVE_BUS_SECONDARY = 1
+};
+
+
+/**
+ *  struct ext_slave_platform_data - Platform data for mpu3050 and mpu6050
+ *  slave devices
+ *
+ *  @get_slave_descr: Function pointer to retrieve the struct ext_slave_descr
+ *                    for this slave
+ *  @irq: the irq number attached to the slave if any.
+ *  @adapt_num: the I2C adapter number.
+ *  @bus: the bus the slave is attached to: enum ext_slave_bus
+ *  @address: the I2C slave address of the slave device.
+ *  @orientation: the mounting matrix of the device relative to MPU.
+ *  @irq_data: private data for the slave irq handler
+ *  @private_data: additional data, user customizable.  Not touched by the MPU
+ *                 driver.
+ *
+ * The orientation matricies are 3x3 rotation matricies
+ * that are applied to the data to rotate from the mounting orientation to the
+ * platform orientation.  The values must be one of 0, 1, or -1 and each row and
+ * column should have exactly 1 non-zero value.
+ */
+struct ext_slave_platform_data {
+	__u8 type;
+	__u32 irq;
+	__u32 adapt_num;
+	__u32 bus;
+	__u8 address;
+	__s8 orientation[9];
+	void *irq_data;
+	void *private_data;
+};
+
+struct fix_pnt_range {
+	__s32 mantissa;
+	__s32 fraction;
+};
+
+static inline long range_fixedpoint_to_long_mg(struct fix_pnt_range rng)
+{
+	return (long)(rng.mantissa * 1000 + rng.fraction / 10);
+}
+
+struct ext_slave_read_trigger {
+	__u8 reg;
+	__u8 value;
+};
+
+/**
+ *  struct ext_slave_descr - Description of the slave device for programming.
+ *
+ *  @suspend:	function pointer to put the device in suspended state
+ *  @resume:	function pointer to put the device in running state
+ *  @read:	function that reads the device data
+ *  @init:	function used to preallocate memory used by the driver
+ *  @exit:	function used to free memory allocated for the driver
+ *  @config:	function used to configure the device
+ *  @get_config:function used to get the device's configuration
+ *
+ *  @name:	text name of the device
+ *  @type:	device type. enum ext_slave_type
+ *  @id:	enum ext_slave_id
+ *  @read_reg:	starting register address to retrieve data.
+ *  @read_len:	length in bytes of the sensor data.  Typically  6.
+ *  @endian:	byte order of the data. enum ext_slave_endian
+ *  @range:	full scale range of the slave ouput: struct fix_pnt_range
+ *  @trigger:	If reading data first requires writing a register this is the
+ *		data to write.
+ *
+ *  Defines the functions and information about the slave the mpu3050 and
+ *  mpu6050 needs to use the slave device.
+ */
+struct ext_slave_descr {
+	int (*init) (void *mlsl_handle,
+		     struct ext_slave_descr *slave,
+		     struct ext_slave_platform_data *pdata);
+	int (*exit) (void *mlsl_handle,
+		     struct ext_slave_descr *slave,
+		     struct ext_slave_platform_data *pdata);
+	int (*suspend) (void *mlsl_handle,
+			struct ext_slave_descr *slave,
+			struct ext_slave_platform_data *pdata);
+	int (*resume) (void *mlsl_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata);
+	int (*read) (void *mlsl_handle,
+		     struct ext_slave_descr *slave,
+		     struct ext_slave_platform_data *pdata,
+		     __u8 *data);
+	int (*config) (void *mlsl_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata,
+		       struct ext_slave_config *config);
+	int (*get_config) (void *mlsl_handle,
+			   struct ext_slave_descr *slave,
+			   struct ext_slave_platform_data *pdata,
+			   struct ext_slave_config *config);
+
+	char *name;
+	__u8 type;
+	__u8 id;
+	__u8 read_reg;
+	__u8 read_len;
+	__u8 endian;
+	struct fix_pnt_range range;
+	struct ext_slave_read_trigger *trigger;
+};
+
+/**
+ * struct mpu_platform_data - Platform data for the mpu driver
+ * @int_config:		Bits [7:3] of the int config register.
+ * @level_shifter:	0: VLogic, 1: VDD
+ * @orientation:	Orientation matrix of the gyroscope
+ *
+ * Contains platform specific information on how to configure the MPU3050 to
+ * work on this platform.  The orientation matricies are 3x3 rotation matricies
+ * that are applied to the data to rotate from the mounting orientation to the
+ * platform orientation.  The values must be one of 0, 1, or -1 and each row and
+ * column should have exactly 1 non-zero value.
+ */
+struct mpu_platform_data {
+	__u8 int_config;
+	__u8 level_shifter;
+	__s8 orientation[GYRO_NUM_AXES * GYRO_NUM_AXES];
+};
+
+#define MPU_IOCTL (0x81) /* Magic number for MPU Iocts */
+/* IOCTL commands for /dev/mpu */
+
+/*--------------------------------------------------------------------------
+ * Deprecated, debugging only
+ */
+#define MPU_SET_MPU_PLATFORM_DATA	\
+	_IOWR(MPU_IOCTL, 0x01, struct mpu_platform_data)
+#define MPU_SET_EXT_SLAVE_PLATFORM_DATA	\
+	_IOWR(MPU_IOCTL, 0x01, struct ext_slave_platform_data)
+/*--------------------------------------------------------------------------*/
+#define MPU_GET_EXT_SLAVE_PLATFORM_DATA	\
+	_IOWR(MPU_IOCTL, 0x02, struct ext_slave_platform_data)
+#define MPU_GET_MPU_PLATFORM_DATA	\
+	_IOWR(MPU_IOCTL, 0x02, struct mpu_platform_data)
+#define MPU_GET_EXT_SLAVE_DESCR	\
+	_IOWR(MPU_IOCTL, 0x02, struct ext_slave_descr)
+
+#define MPU_READ		_IOWR(MPU_IOCTL, 0x10, struct mpu_read_write)
+#define MPU_WRITE		_IOW(MPU_IOCTL,  0x10, struct mpu_read_write)
+#define MPU_READ_MEM		_IOWR(MPU_IOCTL, 0x11, struct mpu_read_write)
+#define MPU_WRITE_MEM		_IOW(MPU_IOCTL,  0x11, struct mpu_read_write)
+#define MPU_READ_FIFO		_IOWR(MPU_IOCTL, 0x12, struct mpu_read_write)
+#define MPU_WRITE_FIFO		_IOW(MPU_IOCTL,  0x12, struct mpu_read_write)
+
+#define MPU_READ_COMPASS	_IOR(MPU_IOCTL, 0x12, __u8)
+#define MPU_READ_ACCEL		_IOR(MPU_IOCTL, 0x13, __u8)
+#define MPU_READ_PRESSURE	_IOR(MPU_IOCTL, 0x14, __u8)
+
+#define MPU_CONFIG_GYRO		_IOW(MPU_IOCTL, 0x20, struct ext_slave_config)
+#define MPU_CONFIG_ACCEL	_IOW(MPU_IOCTL, 0x21, struct ext_slave_config)
+#define MPU_CONFIG_COMPASS	_IOW(MPU_IOCTL, 0x22, struct ext_slave_config)
+#define MPU_CONFIG_PRESSURE	_IOW(MPU_IOCTL, 0x23, struct ext_slave_config)
+
+#define MPU_GET_CONFIG_GYRO	_IOWR(MPU_IOCTL, 0x20, struct ext_slave_config)
+#define MPU_GET_CONFIG_ACCEL	_IOWR(MPU_IOCTL, 0x21, struct ext_slave_config)
+#define MPU_GET_CONFIG_COMPASS	_IOWR(MPU_IOCTL, 0x22, struct ext_slave_config)
+#define MPU_GET_CONFIG_PRESSURE	_IOWR(MPU_IOCTL, 0x23, struct ext_slave_config)
+
+#define MPU_SUSPEND		_IOW(MPU_IOCTL, 0x30, __u32)
+#define MPU_RESUME		_IOW(MPU_IOCTL, 0x31, __u32)
+/* Userspace PM Event response */
+#define MPU_PM_EVENT_HANDLED	_IO(MPU_IOCTL, 0x32)
+
+#define MPU_GET_REQUESTED_SENSORS	_IOR(MPU_IOCTL, 0x40, __u8)
+#define MPU_SET_REQUESTED_SENSORS	_IOW(MPU_IOCTL, 0x40, __u8)
+#define MPU_GET_IGNORE_SYSTEM_SUSPEND	_IOR(MPU_IOCTL, 0x41, __u8)
+#define MPU_SET_IGNORE_SYSTEM_SUSPEND	_IOW(MPU_IOCTL, 0x41, __u8)
+#define MPU_GET_MLDL_STATUS		_IOR(MPU_IOCTL, 0x42, __u8)
+#define MPU_GET_I2C_SLAVES_ENABLED	_IOR(MPU_IOCTL, 0x43, __u8)
+
+
+#endif				/* __MPU_H_ */
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 02/11] misc: mpu3050 Register definition and Private data
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 03/11] misc: mpu3050 /dev/mpu implementation Nathan Royer
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

MPU3050 registers and internal data configuration definion file for the
inv_mpu driver.

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/mldl_cfg.h |  374 +++++++++++++++++++++++++++++++++++++++
 drivers/misc/inv_mpu/mpu3050.h  |  251 ++++++++++++++++++++++++++
 2 files changed, 625 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/mldl_cfg.h
 create mode 100644 drivers/misc/inv_mpu/mpu3050.h

diff --git a/drivers/misc/inv_mpu/mldl_cfg.h b/drivers/misc/inv_mpu/mldl_cfg.h
new file mode 100644
index 0000000..457477b
--- /dev/null
+++ b/drivers/misc/inv_mpu/mldl_cfg.h
@@ -0,0 +1,374 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ *  @addtogroup MLDL
+ *
+ *  @{
+ *      @file   mldl_cfg.h
+ *      @brief  The Motion Library Driver Layer Configuration header file.
+ */
+
+#ifndef __MLDL_CFG_H__
+#define __MLDL_CFG_H__
+
+#include "mltypes.h"
+#include "mlsl.h"
+#include <linux/mpu.h>
+#  include "mpu3050.h"
+
+#include "log.h"
+
+/*************************************************************************
+ *  Sensors Bit definitions
+ *************************************************************************/
+
+#define INV_X_GYRO			(0x0001)
+#define INV_Y_GYRO			(0x0002)
+#define INV_Z_GYRO			(0x0004)
+#define INV_DMP_PROCESSOR		(0x0008)
+
+#define INV_X_ACCEL			(0x0010)
+#define INV_Y_ACCEL			(0x0020)
+#define INV_Z_ACCEL			(0x0040)
+
+#define INV_X_COMPASS			(0x0080)
+#define INV_Y_COMPASS			(0x0100)
+#define INV_Z_COMPASS			(0x0200)
+
+#define INV_X_PRESSURE			(0x0300)
+#define INV_Y_PRESSURE			(0x0800)
+#define INV_Z_PRESSURE			(0x1000)
+
+#define INV_TEMPERATURE			(0x2000)
+#define INV_TIME			(0x4000)
+
+#define INV_THREE_AXIS_GYRO		(0x000F)
+#define INV_THREE_AXIS_ACCEL		(0x0070)
+#define INV_THREE_AXIS_COMPASS		(0x0380)
+#define INV_THREE_AXIS_PRESSURE		(0x1C00)
+
+#define INV_FIVE_AXIS			(0x007B)
+#define INV_SIX_AXIS_GYRO_ACCEL		(0x007F)
+#define INV_SIX_AXIS_ACCEL_COMPASS	(0x03F0)
+#define INV_NINE_AXIS			(0x03FF)
+#define INV_ALL_SENSORS			(0x7FFF)
+
+#define MPL_PROD_KEY(ver, rev) (ver * 100 + rev)
+
+/* -------------------------------------------------------------------------- */
+struct mpu_ram {
+	__u16 length;
+	__u8 *ram;
+};
+
+struct mpu_gyro_cfg {
+	__u8 int_config;
+	__u8 ext_sync;
+	__u8 full_scale;
+	__u8 lpf;
+	__u8 clk_src;
+	__u8 divider;
+	__u8 dmp_enable;
+	__u8 fifo_enable;
+	__u8 dmp_cfg1;
+	__u8 dmp_cfg2;
+};
+
+/* Offset registers that can be calibrated */
+struct mpu_offsets {
+	__u8	tc[GYRO_NUM_AXES];
+	__u16	gyro[GYRO_NUM_AXES];
+};
+
+/* Chip related information that can be read and verified */
+struct mpu_chip_info {
+	__u8 addr;
+	__u8 product_revision;
+	__u8 silicon_revision;
+	__u8 product_id;
+	__u16 gyro_sens_trim;
+	/* Only used for MPU6050 */
+	__u16 accel_sens_trim;
+};
+
+
+struct inv_mpu_cfg {
+	__u32 requested_sensors;
+	__u8 ignore_system_suspend;
+};
+
+/* Driver related state information */
+struct inv_mpu_state {
+#define MPU_GYRO_IS_SUSPENDED		(0x01 << EXT_SLAVE_TYPE_GYROSCOPE)
+#define MPU_ACCEL_IS_SUSPENDED		(0x01 << EXT_SLAVE_TYPE_ACCEL)
+#define MPU_COMPASS_IS_SUSPENDED	(0x01 << EXT_SLAVE_TYPE_COMPASS)
+#define MPU_PRESSURE_IS_SUSPENDED	(0x01 << EXT_SLAVE_TYPE_PRESSURE)
+#define MPU_GYRO_IS_BYPASSED		(0x10)
+#define MPU_DMP_IS_SUSPENDED		(0x20)
+#define MPU_GYRO_NEEDS_RESET		(0x40)
+	__u8 status;
+	/* 0-1 for 3050, bitfield of BIT_SLVx_DLY_EN, x = [0..4] */
+	__u8 i2c_slaves_enabled;
+};
+
+/* Platform data for the MPU */
+struct mldl_cfg {
+	struct mpu_ram			*mpu_ram;
+	struct mpu_gyro_cfg		*mpu_gyro_cfg;
+	struct mpu_offsets		*mpu_offsets;
+	struct mpu_chip_info		*mpu_chip_info;
+
+	/* MPU Related stored status and info */
+	struct inv_mpu_cfg		*inv_mpu_cfg;
+	struct inv_mpu_state		*inv_mpu_state;
+
+	/* Slave related information */
+	struct ext_slave_descr		*slave[EXT_SLAVE_NUM_TYPES];
+	/* Platform Data */
+	struct mpu_platform_data	*pdata;
+	struct ext_slave_platform_data	*pdata_slave[EXT_SLAVE_NUM_TYPES];
+};
+
+/* -------------------------------------------------------------------------- */
+
+int inv_mpu_open(struct mldl_cfg *mldl_cfg,
+		 void *mlsl_handle,
+		 void *accel_handle,
+		 void *compass_handle,
+		 void *pressure_handle);
+int inv_mpu_close(struct mldl_cfg *mldl_cfg,
+		  void *mlsl_handle,
+		  void *accel_handle,
+		  void *compass_handle,
+		  void *pressure_handle);
+int inv_mpu_resume(struct mldl_cfg *mldl_cfg,
+		   void *gyro_handle,
+		   void *accel_handle,
+		   void *compass_handle,
+		   void *pressure_handle,
+		   unsigned long sensors);
+int inv_mpu_suspend(struct mldl_cfg *mldl_cfg,
+		    void *gyro_handle,
+		    void *accel_handle,
+		    void *compass_handle,
+		    void *pressure_handle,
+		    unsigned long sensors);
+
+/* Slave Read functions */
+int inv_mpu_slave_read(struct mldl_cfg *mldl_cfg,
+		       void *gyro_handle,
+		       void *slave_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata,
+		       unsigned char *data);
+static inline int inv_mpu_read_accel(struct mldl_cfg *mldl_cfg,
+				     void *gyro_handle,
+				     void *accel_handle, unsigned char *data)
+{
+	if (!mldl_cfg) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_slave_read(
+		mldl_cfg, gyro_handle, accel_handle,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_ACCEL],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_ACCEL],
+		data);
+}
+
+static inline int inv_mpu_read_compass(struct mldl_cfg *mldl_cfg,
+				       void *gyro_handle,
+				       void *compass_handle,
+				       unsigned char *data)
+{
+	if (!mldl_cfg) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_slave_read(
+		mldl_cfg, gyro_handle, compass_handle,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_COMPASS],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_COMPASS],
+		data);
+}
+
+static inline int inv_mpu_read_pressure(struct mldl_cfg *mldl_cfg,
+					void *gyro_handle,
+					void *pressure_handle,
+					unsigned char *data)
+{
+	if (!mldl_cfg) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_slave_read(
+		mldl_cfg, gyro_handle, pressure_handle,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_PRESSURE],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_PRESSURE],
+		data);
+}
+
+int gyro_config(void *mlsl_handle,
+		struct mldl_cfg *mldl_cfg,
+		struct ext_slave_config *data);
+
+/* Slave Config functions */
+int inv_mpu_slave_config(struct mldl_cfg *mldl_cfg,
+			 void *gyro_handle,
+			 void *slave_handle,
+			 struct ext_slave_config *data,
+			 struct ext_slave_descr *slave,
+			 struct ext_slave_platform_data *pdata);
+static inline int inv_mpu_config_accel(struct mldl_cfg *mldl_cfg,
+				       void *gyro_handle,
+				       void *accel_handle,
+				       struct ext_slave_config *data)
+{
+	if (!mldl_cfg) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_slave_config(
+		mldl_cfg, gyro_handle, accel_handle, data,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_ACCEL],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_ACCEL]);
+}
+
+static inline int inv_mpu_config_compass(struct mldl_cfg *mldl_cfg,
+					 void *gyro_handle,
+					 void *compass_handle,
+					 struct ext_slave_config *data)
+{
+	if (!mldl_cfg) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_slave_config(
+		mldl_cfg, gyro_handle, compass_handle, data,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_COMPASS],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_COMPASS]);
+}
+
+static inline int inv_mpu_config_pressure(struct mldl_cfg *mldl_cfg,
+					  void *gyro_handle,
+					  void *pressure_handle,
+					  struct ext_slave_config *data)
+{
+	if (!mldl_cfg) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_slave_config(
+		mldl_cfg, gyro_handle, pressure_handle, data,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_PRESSURE],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_PRESSURE]);
+}
+
+int gyro_get_config(void *mlsl_handle,
+		struct mldl_cfg *mldl_cfg,
+		struct ext_slave_config *data);
+
+/* Slave get config functions */
+int inv_mpu_get_slave_config(struct mldl_cfg *mldl_cfg,
+			     void *gyro_handle,
+			     void *slave_handle,
+			     struct ext_slave_config *data,
+			     struct ext_slave_descr *slave,
+			     struct ext_slave_platform_data *pdata);
+
+static inline int inv_mpu_get_accel_config(struct mldl_cfg *mldl_cfg,
+					   void *gyro_handle,
+					   void *accel_handle,
+					   struct ext_slave_config *data)
+{
+	if (!mldl_cfg) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_get_slave_config(
+		mldl_cfg, gyro_handle, accel_handle, data,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_ACCEL],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_ACCEL]);
+}
+
+static inline int inv_mpu_get_compass_config(struct mldl_cfg *mldl_cfg,
+					     void *gyro_handle,
+					     void *compass_handle,
+					     struct ext_slave_config *data)
+{
+	if (!mldl_cfg || !(mldl_cfg->pdata)) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_get_slave_config(
+		mldl_cfg, gyro_handle, compass_handle, data,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_COMPASS],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_COMPASS]);
+}
+
+static inline int inv_mpu_get_pressure_config(struct mldl_cfg *mldl_cfg,
+					      void *gyro_handle,
+					      void *pressure_handle,
+					      struct ext_slave_config *data)
+{
+	if (!mldl_cfg || !(mldl_cfg->pdata)) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	return inv_mpu_get_slave_config(
+		mldl_cfg, gyro_handle, pressure_handle, data,
+		mldl_cfg->slave[EXT_SLAVE_TYPE_PRESSURE],
+		mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_PRESSURE]);
+}
+
+/* -------------------------------------------------------------------------- */
+
+static inline
+long inv_mpu_get_sampling_rate_hz(struct mpu_gyro_cfg *gyro_cfg)
+{
+	if (((gyro_cfg->lpf) == 0) || ((gyro_cfg->lpf) == 7))
+		return 8000L / (gyro_cfg->divider + 1);
+	else
+		return 1000L / (gyro_cfg->divider + 1);
+}
+
+static inline
+long inv_mpu_get_sampling_period_us(struct mpu_gyro_cfg *gyro_cfg)
+{
+	if (((gyro_cfg->lpf) == 0) || ((gyro_cfg->lpf) == 7))
+		return (long) (1000000L * (gyro_cfg->divider + 1)) / 8000L;
+	else
+		return (long) (1000000L * (gyro_cfg->divider + 1)) / 1000L;
+}
+
+#endif				/* __MLDL_CFG_H__ */
+
+/**
+ * @}
+ */
diff --git a/drivers/misc/inv_mpu/mpu3050.h b/drivers/misc/inv_mpu/mpu3050.h
new file mode 100644
index 0000000..02af16e
--- /dev/null
+++ b/drivers/misc/inv_mpu/mpu3050.h
@@ -0,0 +1,251 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+#ifndef __MPU_H_
+#error Do not include this file directly.  Include mpu.h instead.
+#endif
+
+#ifndef __MPU3050_H_
+#define __MPU3050_H_
+
+#include <linux/types.h>
+
+
+#define MPU_NAME "mpu3050"
+#define DEFAULT_MPU_SLAVEADDR       0x68
+
+/*==== MPU REGISTER SET ====*/
+enum mpu_register {
+	MPUREG_WHO_AM_I = 0,	/* 00 0x00 */
+	MPUREG_PRODUCT_ID,	/* 01 0x01 */
+	MPUREG_02_RSVD,		/* 02 0x02 */
+	MPUREG_03_RSVD,		/* 03 0x03 */
+	MPUREG_04_RSVD,		/* 04 0x04 */
+	MPUREG_XG_OFFS_TC,	/* 05 0x05 */
+	MPUREG_06_RSVD,		/* 06 0x06 */
+	MPUREG_07_RSVD,		/* 07 0x07 */
+	MPUREG_YG_OFFS_TC,	/* 08 0x08 */
+	MPUREG_09_RSVD,		/* 09 0x09 */
+	MPUREG_0A_RSVD,		/* 10 0x0a */
+	MPUREG_ZG_OFFS_TC,	/* 11 0x0b */
+	MPUREG_X_OFFS_USRH,	/* 12 0x0c */
+	MPUREG_X_OFFS_USRL,	/* 13 0x0d */
+	MPUREG_Y_OFFS_USRH,	/* 14 0x0e */
+	MPUREG_Y_OFFS_USRL,	/* 15 0x0f */
+	MPUREG_Z_OFFS_USRH,	/* 16 0x10 */
+	MPUREG_Z_OFFS_USRL,	/* 17 0x11 */
+	MPUREG_FIFO_EN1,	/* 18 0x12 */
+	MPUREG_FIFO_EN2,	/* 19 0x13 */
+	MPUREG_AUX_SLV_ADDR,	/* 20 0x14 */
+	MPUREG_SMPLRT_DIV,	/* 21 0x15 */
+	MPUREG_DLPF_FS_SYNC,	/* 22 0x16 */
+	MPUREG_INT_CFG,		/* 23 0x17 */
+	MPUREG_ACCEL_BURST_ADDR,/* 24 0x18 */
+	MPUREG_19_RSVD,		/* 25 0x19 */
+	MPUREG_INT_STATUS,	/* 26 0x1a */
+	MPUREG_TEMP_OUT_H,	/* 27 0x1b */
+	MPUREG_TEMP_OUT_L,	/* 28 0x1c */
+	MPUREG_GYRO_XOUT_H,	/* 29 0x1d */
+	MPUREG_GYRO_XOUT_L,	/* 30 0x1e */
+	MPUREG_GYRO_YOUT_H,	/* 31 0x1f */
+	MPUREG_GYRO_YOUT_L,	/* 32 0x20 */
+	MPUREG_GYRO_ZOUT_H,	/* 33 0x21 */
+	MPUREG_GYRO_ZOUT_L,	/* 34 0x22 */
+	MPUREG_23_RSVD,		/* 35 0x23 */
+	MPUREG_24_RSVD,		/* 36 0x24 */
+	MPUREG_25_RSVD,		/* 37 0x25 */
+	MPUREG_26_RSVD,		/* 38 0x26 */
+	MPUREG_27_RSVD,		/* 39 0x27 */
+	MPUREG_28_RSVD,		/* 40 0x28 */
+	MPUREG_29_RSVD,		/* 41 0x29 */
+	MPUREG_2A_RSVD,		/* 42 0x2a */
+	MPUREG_2B_RSVD,		/* 43 0x2b */
+	MPUREG_2C_RSVD,		/* 44 0x2c */
+	MPUREG_2D_RSVD,		/* 45 0x2d */
+	MPUREG_2E_RSVD,		/* 46 0x2e */
+	MPUREG_2F_RSVD,		/* 47 0x2f */
+	MPUREG_30_RSVD,		/* 48 0x30 */
+	MPUREG_31_RSVD,		/* 49 0x31 */
+	MPUREG_32_RSVD,		/* 50 0x32 */
+	MPUREG_33_RSVD,		/* 51 0x33 */
+	MPUREG_34_RSVD,		/* 52 0x34 */
+	MPUREG_DMP_CFG_1,	/* 53 0x35 */
+	MPUREG_DMP_CFG_2,	/* 54 0x36 */
+	MPUREG_BANK_SEL,	/* 55 0x37 */
+	MPUREG_MEM_START_ADDR,	/* 56 0x38 */
+	MPUREG_MEM_R_W,		/* 57 0x39 */
+	MPUREG_FIFO_COUNTH,	/* 58 0x3a */
+	MPUREG_FIFO_COUNTL,	/* 59 0x3b */
+	MPUREG_FIFO_R_W,	/* 60 0x3c */
+	MPUREG_USER_CTRL,	/* 61 0x3d */
+	MPUREG_PWR_MGM,		/* 62 0x3e */
+	MPUREG_3F_RSVD,		/* 63 0x3f */
+	NUM_OF_MPU_REGISTERS	/* 64 0x40 */
+};
+
+/*==== BITS FOR MPU ====*/
+
+/*---- MPU 'FIFO_EN1' register (12) ----*/
+#define BIT_TEMP_OUT                0x80
+#define BIT_GYRO_XOUT               0x40
+#define BIT_GYRO_YOUT               0x20
+#define BIT_GYRO_ZOUT               0x10
+#define BIT_ACCEL_XOUT              0x08
+#define BIT_ACCEL_YOUT              0x04
+#define BIT_ACCEL_ZOUT              0x02
+#define BIT_AUX_1OUT                0x01
+/*---- MPU 'FIFO_EN2' register (13) ----*/
+#define BIT_AUX_2OUT                0x02
+#define BIT_AUX_3OUT                0x01
+/*---- MPU 'DLPF_FS_SYNC' register (16) ----*/
+#define BITS_EXT_SYNC_NONE          0x00
+#define BITS_EXT_SYNC_TEMP          0x20
+#define BITS_EXT_SYNC_GYROX         0x40
+#define BITS_EXT_SYNC_GYROY         0x60
+#define BITS_EXT_SYNC_GYROZ         0x80
+#define BITS_EXT_SYNC_ACCELX        0xA0
+#define BITS_EXT_SYNC_ACCELY        0xC0
+#define BITS_EXT_SYNC_ACCELZ        0xE0
+#define BITS_EXT_SYNC_MASK          0xE0
+#define BITS_FS_250DPS              0x00
+#define BITS_FS_500DPS              0x08
+#define BITS_FS_1000DPS             0x10
+#define BITS_FS_2000DPS             0x18
+#define BITS_FS_MASK                0x18
+#define BITS_DLPF_CFG_256HZ_NOLPF2  0x00
+#define BITS_DLPF_CFG_188HZ         0x01
+#define BITS_DLPF_CFG_98HZ          0x02
+#define BITS_DLPF_CFG_42HZ          0x03
+#define BITS_DLPF_CFG_20HZ          0x04
+#define BITS_DLPF_CFG_10HZ          0x05
+#define BITS_DLPF_CFG_5HZ           0x06
+#define BITS_DLPF_CFG_2100HZ_NOLPF  0x07
+#define BITS_DLPF_CFG_MASK          0x07
+/*---- MPU 'INT_CFG' register (17) ----*/
+#define BIT_ACTL                    0x80
+#define BIT_ACTL_LOW                0x80
+#define BIT_ACTL_HIGH               0x00
+#define BIT_OPEN                    0x40
+#define BIT_OPEN_DRAIN              0x40
+#define BIT_PUSH_PULL               0x00
+#define BIT_LATCH_INT_EN            0x20
+#define BIT_INT_PULSE_WIDTH_50US    0x00
+#define BIT_INT_ANYRD_2CLEAR        0x10
+#define BIT_INT_STAT_READ_2CLEAR    0x00
+#define BIT_MPU_RDY_EN              0x04
+#define BIT_DMP_INT_EN              0x02
+#define BIT_RAW_RDY_EN              0x01
+/*---- MPU 'INT_STATUS' register (1A) ----*/
+#define BIT_INT_STATUS_FIFO_OVERLOW 0x80
+#define BIT_MPU_RDY                 0x04
+#define BIT_DMP_INT                 0x02
+#define BIT_RAW_RDY                 0x01
+/*---- MPU 'BANK_SEL' register (37) ----*/
+#define BIT_PRFTCH_EN               0x20
+#define BIT_CFG_USER_BANK           0x10
+#define BITS_MEM_SEL                0x0f
+/*---- MPU 'USER_CTRL' register (3D) ----*/
+#define BIT_DMP_EN                  0x80
+#define BIT_FIFO_EN                 0x40
+#define BIT_AUX_IF_EN               0x20
+#define BIT_AUX_RD_LENG             0x10
+#define BIT_AUX_IF_RST              0x08
+#define BIT_DMP_RST                 0x04
+#define BIT_FIFO_RST                0x02
+#define BIT_GYRO_RST                0x01
+/*---- MPU 'PWR_MGM' register (3E) ----*/
+#define BIT_H_RESET                 0x80
+#define BIT_SLEEP                   0x40
+#define BIT_STBY_XG                 0x20
+#define BIT_STBY_YG                 0x10
+#define BIT_STBY_ZG                 0x08
+#define BITS_CLKSEL                 0x07
+
+/*---- MPU Silicon Revision ----*/
+#define MPU_SILICON_REV_A4           1	/* MPU A4 Device */
+#define MPU_SILICON_REV_B1           2	/* MPU B1 Device */
+#define MPU_SILICON_REV_B4           3	/* MPU B4 Device */
+#define MPU_SILICON_REV_B6           4	/* MPU B6 Device */
+
+/*---- MPU Memory ----*/
+#define MPU_MEM_BANK_SIZE           (256)
+#define FIFO_HW_SIZE                (512)
+
+enum MPU_MEMORY_BANKS {
+	MPU_MEM_RAM_BANK_0 = 0,
+	MPU_MEM_RAM_BANK_1,
+	MPU_MEM_RAM_BANK_2,
+	MPU_MEM_RAM_BANK_3,
+	MPU_MEM_NUM_RAM_BANKS,
+	MPU_MEM_OTP_BANK_0 = MPU_MEM_NUM_RAM_BANKS,
+	/* This one is always last */
+	MPU_MEM_NUM_BANKS
+};
+
+/*---- structure containing control variables used by MLDL ----*/
+/*---- MPU clock source settings ----*/
+/*---- MPU filter selections ----*/
+enum mpu_filter {
+	MPU_FILTER_256HZ_NOLPF2 = 0,
+	MPU_FILTER_188HZ,
+	MPU_FILTER_98HZ,
+	MPU_FILTER_42HZ,
+	MPU_FILTER_20HZ,
+	MPU_FILTER_10HZ,
+	MPU_FILTER_5HZ,
+	MPU_FILTER_2100HZ_NOLPF,
+	NUM_MPU_FILTER
+};
+
+enum mpu_fullscale {
+	MPU_FS_250DPS = 0,
+	MPU_FS_500DPS,
+	MPU_FS_1000DPS,
+	MPU_FS_2000DPS,
+	NUM_MPU_FS
+};
+
+enum mpu_clock_sel {
+	MPU_CLK_SEL_INTERNAL = 0,
+	MPU_CLK_SEL_PLLGYROX,
+	MPU_CLK_SEL_PLLGYROY,
+	MPU_CLK_SEL_PLLGYROZ,
+	MPU_CLK_SEL_PLLEXT32K,
+	MPU_CLK_SEL_PLLEXT19M,
+	MPU_CLK_SEL_RESERVED,
+	MPU_CLK_SEL_STOP,
+	NUM_CLK_SEL
+};
+
+enum mpu_ext_sync {
+	MPU_EXT_SYNC_NONE = 0,
+	MPU_EXT_SYNC_TEMP,
+	MPU_EXT_SYNC_GYROX,
+	MPU_EXT_SYNC_GYROY,
+	MPU_EXT_SYNC_GYROZ,
+	MPU_EXT_SYNC_ACCELX,
+	MPU_EXT_SYNC_ACCELY,
+	MPU_EXT_SYNC_ACCELZ,
+	NUM_MPU_EXT_SYNC
+};
+
+#define DLPF_FS_SYNC_VALUE(ext_sync, full_scale, lpf) \
+	((ext_sync << 5) | (full_scale << 3) | lpf)
+
+#endif				/* __MPU3050_H_ */
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 03/11] misc: mpu3050 /dev/mpu implementation.
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
  2011-07-01  2:18 ` [PATCH 02/11] misc: mpu3050 Register definition and Private data Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 04/11] misc: IRQ handling for MPU3050 and slave devices Nathan Royer
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Implements i2c probing, receives registration of slaves, handles file
operations, and power management.

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/mpu-dev.c | 1247 ++++++++++++++++++++++++++++++++++++++++
 drivers/misc/inv_mpu/mpu-dev.h |   36 ++
 2 files changed, 1283 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/mpu-dev.c
 create mode 100644 drivers/misc/inv_mpu/mpu-dev.h

diff --git a/drivers/misc/inv_mpu/mpu-dev.c b/drivers/misc/inv_mpu/mpu-dev.c
new file mode 100644
index 0000000..977631d
--- /dev/null
+++ b/drivers/misc/inv_mpu/mpu-dev.c
@@ -0,0 +1,1247 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/stat.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/signal.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+#include <linux/pm.h>
+#include <linux/mutex.h>
+#include <linux/suspend.h>
+#include <linux/poll.h>
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include "mpuirq.h"
+#include "slaveirq.h"
+#include "mlsl.h"
+#include "mldl_cfg.h"
+#include <linux/mpu.h>
+
+
+/* Platform data for the MPU */
+struct mpu_private_data {
+	struct miscdevice dev;
+	struct i2c_client *client;
+
+	/* mldl_cfg data */
+	struct mldl_cfg mldl_cfg;
+	struct mpu_ram		mpu_ram;
+	struct mpu_gyro_cfg	mpu_gyro_cfg;
+	struct mpu_offsets	mpu_offsets;
+	struct mpu_chip_info	mpu_chip_info;
+	struct inv_mpu_cfg	inv_mpu_cfg;
+	struct inv_mpu_state	inv_mpu_state;
+
+	struct mutex mutex;
+	wait_queue_head_t mpu_event_wait;
+	struct completion completion;
+	struct timer_list timeout;
+	struct notifier_block nb;
+	struct mpuirq_data mpu_pm_event;
+	int response_timeout;	/* In seconds */
+	unsigned long event;
+	int pid;
+	struct module *slave_modules[EXT_SLAVE_NUM_TYPES];
+};
+
+struct mpu_private_data *mpu_private_data;
+
+static void mpu_pm_timeout(u_long data)
+{
+	struct mpu_private_data *mpu = (struct mpu_private_data *)data;
+	struct i2c_client *client = mpu->client;
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+	complete(&mpu->completion);
+}
+
+static int mpu_pm_notifier_callback(struct notifier_block *nb,
+				    unsigned long event, void *unused)
+{
+	struct mpu_private_data *mpu =
+	    container_of(nb, struct mpu_private_data, nb);
+	struct i2c_client *client = mpu->client;
+	struct timeval event_time;
+	dev_dbg(&client->adapter->dev, "%s: %ld\n", __func__, event);
+
+	/* Prevent the file handle from being closed before we initialize
+	   the completion event */
+	mutex_lock(&mpu->mutex);
+	if (!(mpu->pid) ||
+	    (event != PM_SUSPEND_PREPARE && event != PM_POST_SUSPEND)) {
+		mutex_unlock(&mpu->mutex);
+		return NOTIFY_OK;
+	}
+
+	do_gettimeofday(&event_time);
+	mpu->mpu_pm_event.interruptcount++;
+	mpu->mpu_pm_event.irqtime =
+	    (((long long)event_time.tv_sec) << 32) + event_time.tv_usec;
+	mpu->mpu_pm_event.data_type = MPUIRQ_DATA_TYPE_PM_EVENT;
+	mpu->mpu_pm_event.data = mpu->event;
+
+	if (event == PM_SUSPEND_PREPARE)
+		mpu->event = MPU_PM_EVENT_SUSPEND_PREPARE;
+	if (event == PM_POST_SUSPEND)
+		mpu->event = MPU_PM_EVENT_POST_SUSPEND;
+
+	if (mpu->response_timeout > 0) {
+		mpu->timeout.expires = jiffies + mpu->response_timeout * HZ;
+		add_timer(&mpu->timeout);
+	}
+	INIT_COMPLETION(mpu->completion);
+	mutex_unlock(&mpu->mutex);
+
+	wake_up_interruptible(&mpu->mpu_event_wait);
+	wait_for_completion(&mpu->completion);
+	del_timer_sync(&mpu->timeout);
+	dev_dbg(&client->adapter->dev, "%s: %ld DONE\n", __func__, event);
+	return NOTIFY_OK;
+}
+
+static int mpu_dev_open(struct inode *inode, struct file *file)
+{
+	struct mpu_private_data *mpu =
+	    container_of(file->private_data, struct mpu_private_data, dev);
+	struct i2c_client *client = mpu->client;
+	int result;
+	int ii;
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+	dev_dbg(&client->adapter->dev, "current->pid %d\n", current->pid);
+
+	result = mutex_lock_interruptible(&mpu->mutex);
+	if (mpu->pid) {
+		mutex_unlock(&mpu->mutex);
+		return -EBUSY;
+	}
+	mpu->pid = current->pid;
+
+	/* Reset the sensors to the default */
+	if (result) {
+		dev_err(&client->adapter->dev,
+			"%s: mutex_lock_interruptible returned %d\n",
+			__func__, result);
+		return result;
+	}
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++)
+		__module_get(mpu->slave_modules[ii]);
+
+	mutex_unlock(&mpu->mutex);
+	return 0;
+}
+
+/* close function - called when the "file" /dev/mpu is closed in userspace   */
+static int mpu_release(struct inode *inode, struct file *file)
+{
+	struct mpu_private_data *mpu =
+	    container_of(file->private_data, struct mpu_private_data, dev);
+	struct i2c_client *client = mpu->client;
+	struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg;
+	int result = 0;
+	int ii;
+	struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES];
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!pdata_slave[ii])
+			slave_adapter[ii] = NULL;
+		else
+			slave_adapter[ii] =
+				i2c_get_adapter(pdata_slave[ii]->adapt_num);
+	}
+	slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter;
+
+	mutex_lock(&mpu->mutex);
+	mldl_cfg->inv_mpu_cfg->requested_sensors = 0;
+	result = inv_mpu_suspend(mldl_cfg,
+				slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+				slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+				slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+				slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+				INV_ALL_SENSORS);
+	mpu->pid = 0;
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++)
+		module_put(mpu->slave_modules[ii]);
+
+	mutex_unlock(&mpu->mutex);
+	complete(&mpu->completion);
+	dev_dbg(&client->adapter->dev, "mpu_release\n");
+
+	return result;
+}
+
+/* read function called when from /dev/mpu is read.  Read from the FIFO */
+static ssize_t mpu_read(struct file *file,
+			char __user *buf, size_t count, loff_t *offset)
+{
+	struct mpu_private_data *mpu =
+	    container_of(file->private_data, struct mpu_private_data, dev);
+	struct i2c_client *client = mpu->client;
+	size_t len = sizeof(mpu->mpu_pm_event) + sizeof(unsigned long);
+	int err;
+
+	if (!mpu->event && (!(file->f_flags & O_NONBLOCK)))
+		wait_event_interruptible(mpu->mpu_event_wait, mpu->event);
+
+	if (!mpu->event || !buf
+	    || count < sizeof(mpu->mpu_pm_event) + sizeof(unsigned long))
+		return 0;
+
+	err = copy_to_user(buf, &mpu->mpu_pm_event, sizeof(mpu->mpu_pm_event));
+	if (err) {
+		dev_err(&client->adapter->dev,
+			"Copy to user returned %d\n", err);
+		return -EFAULT;
+	}
+	mpu->event = 0;
+	return len;
+}
+
+static unsigned int mpu_poll(struct file *file, struct poll_table_struct *poll)
+{
+	struct mpu_private_data *mpu =
+	    container_of(file->private_data, struct mpu_private_data, dev);
+	int mask = 0;
+
+	poll_wait(file, &mpu->mpu_event_wait, poll);
+	if (mpu->event)
+		mask |= POLLIN | POLLRDNORM;
+	return mask;
+}
+
+static int mpu_dev_ioctl_get_ext_slave_platform_data(
+	struct i2c_client *client,
+	struct ext_slave_platform_data __user *arg)
+{
+	struct mpu_private_data *mpu =
+	    (struct mpu_private_data *)i2c_get_clientdata(client);
+	struct ext_slave_platform_data *pdata_slave;
+	struct ext_slave_platform_data local_pdata_slave;
+
+	if (copy_from_user(&local_pdata_slave, arg, sizeof(local_pdata_slave)))
+		return -EFAULT;
+
+	if (local_pdata_slave.type >= EXT_SLAVE_NUM_TYPES)
+		return -EINVAL;
+
+	pdata_slave = mpu->mldl_cfg.pdata_slave[local_pdata_slave.type];
+	/* All but private data and irq_data */
+	if (!pdata_slave)
+		return -ENODEV;
+	if (copy_to_user(arg, pdata_slave, sizeof(*pdata_slave)))
+		return -EFAULT;
+	return 0;
+}
+
+static int mpu_dev_ioctl_get_mpu_platform_data(
+	struct i2c_client *client,
+	struct mpu_platform_data __user *arg)
+{
+	struct mpu_private_data *mpu =
+	    (struct mpu_private_data *)i2c_get_clientdata(client);
+	struct mpu_platform_data *pdata = mpu->mldl_cfg.pdata;
+
+	if (copy_to_user(arg, pdata, sizeof(pdata)))
+		return -EFAULT;
+	return 0;
+}
+
+static int mpu_dev_ioctl_get_ext_slave_descr(
+	struct i2c_client *client,
+	struct ext_slave_descr __user *arg)
+{
+	struct mpu_private_data *mpu =
+	    (struct mpu_private_data *)i2c_get_clientdata(client);
+	struct ext_slave_descr *slave;
+	struct ext_slave_descr local_slave;
+
+	if (copy_from_user(&local_slave, arg, sizeof(local_slave)))
+		return -EFAULT;
+
+	if (local_slave.type >= EXT_SLAVE_NUM_TYPES)
+		return -EINVAL;
+
+	slave = mpu->mldl_cfg.slave[local_slave.type];
+	/* All but private data and irq_data */
+	if (!slave)
+		return -ENODEV;
+	if (copy_to_user(arg, slave, sizeof(*slave)))
+		return -EFAULT;
+	return 0;
+}
+
+
+/**
+ * slave_config() - Pass a requested slave configuration to the slave sensor
+ *
+ * @adapter the adaptor to use to communicate with the slave
+ * @mldl_cfg the mldl configuration structuer
+ * @slave pointer to the slave descriptor
+ * @usr_config The configuration to pass to the slave sensor
+ *
+ * returns 0 or non-zero error code
+ */
+static int inv_mpu_config(struct mldl_cfg *mldl_cfg,
+			void *gyro_adapter,
+			struct ext_slave_config __user *usr_config)
+{
+	int retval = 0;
+	struct ext_slave_config config;
+
+	retval = copy_from_user(&config, usr_config, sizeof(config));
+	if (retval)
+		return -EFAULT;
+
+	if (config.len && config.data) {
+		void *data;
+		data = kmalloc(config.len, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+
+		retval = copy_from_user(data,
+					(void __user *)config.data, config.len);
+		if (retval) {
+			retval = -EFAULT;
+			kfree(data);
+			return retval;
+		}
+		config.data = data;
+	}
+	retval = gyro_config(gyro_adapter, mldl_cfg, &config);
+	kfree(config.data);
+	return retval;
+}
+
+static int inv_mpu_get_config(struct mldl_cfg *mldl_cfg,
+			    void *gyro_adapter,
+			    struct ext_slave_config __user *usr_config)
+{
+	int retval = 0;
+	struct ext_slave_config config;
+	void *user_data;
+
+	retval = copy_from_user(&config, usr_config, sizeof(config));
+	if (retval)
+		return -EFAULT;
+
+	user_data = config.data;
+	if (config.len && config.data) {
+		void *data;
+		data = kmalloc(config.len, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+
+		retval = copy_from_user(data,
+					(void __user *)config.data, config.len);
+		if (retval) {
+			retval = -EFAULT;
+			kfree(data);
+			return retval;
+		}
+		config.data = data;
+	}
+	retval = gyro_get_config(gyro_adapter, mldl_cfg, &config);
+	if (!retval)
+		retval = copy_to_user((unsigned char __user *)user_data,
+				config.data, config.len);
+	kfree(config.data);
+	return retval;
+}
+
+static int slave_config(struct mldl_cfg *mldl_cfg,
+			void *gyro_adapter,
+			void *slave_adapter,
+			struct ext_slave_descr *slave,
+			struct ext_slave_platform_data *pdata,
+			struct ext_slave_config __user *usr_config)
+{
+	int retval = 0;
+	struct ext_slave_config config;
+	if ((!slave) || (!slave->config))
+		return -ENODEV;
+
+	retval = copy_from_user(&config, usr_config, sizeof(config));
+	if (retval)
+		return -EFAULT;
+
+	if (config.len && config.data) {
+		void *data;
+		data = kmalloc(config.len, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+
+		retval = copy_from_user(data,
+					(void __user *)config.data, config.len);
+		if (retval) {
+			retval = -EFAULT;
+			kfree(data);
+			return retval;
+		}
+		config.data = data;
+	}
+	retval = inv_mpu_slave_config(mldl_cfg, gyro_adapter, slave_adapter,
+				      &config, slave, pdata);
+	kfree(config.data);
+	return retval;
+}
+
+static int slave_get_config(struct mldl_cfg *mldl_cfg,
+			    void *gyro_adapter,
+			    void *slave_adapter,
+			    struct ext_slave_descr *slave,
+			    struct ext_slave_platform_data *pdata,
+			    struct ext_slave_config __user *usr_config)
+{
+	int retval = 0;
+	struct ext_slave_config config;
+	void *user_data;
+	if (!(slave) || !(slave->get_config))
+		return -ENODEV;
+
+	retval = copy_from_user(&config, usr_config, sizeof(config));
+	if (retval)
+		return -EFAULT;
+
+	user_data = config.data;
+	if (config.len && config.data) {
+		void *data;
+		data = kmalloc(config.len, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+
+		retval = copy_from_user(data,
+					(void __user *)config.data, config.len);
+		if (retval) {
+			retval = -EFAULT;
+			kfree(data);
+			return retval;
+		}
+		config.data = data;
+	}
+	retval = inv_mpu_get_slave_config(mldl_cfg, gyro_adapter,
+					  slave_adapter, &config, slave, pdata);
+	if (retval) {
+		kfree(config.data);
+		return retval;
+	}
+	retval = copy_to_user((unsigned char __user *)user_data,
+			      config.data, config.len);
+	kfree(config.data);
+	return retval;
+}
+
+static int inv_slave_read(struct mldl_cfg *mldl_cfg,
+			  void *gyro_adapter,
+			  void *slave_adapter,
+			  struct ext_slave_descr *slave,
+			  struct ext_slave_platform_data *pdata,
+			  void __user *usr_data)
+{
+	int retval;
+	unsigned char *data;
+	data = kzalloc(slave->read_len, GFP_KERNEL);
+	if (!data)
+		return -EFAULT;
+
+	retval = inv_mpu_slave_read(mldl_cfg, gyro_adapter, slave_adapter,
+				    slave, pdata, data);
+
+	if ((!retval) &&
+	    (copy_to_user((unsigned char __user *)usr_data,
+			  data, slave->read_len)))
+		retval = -EFAULT;
+
+	kfree(data);
+	return retval;
+}
+
+static int mpu_handle_mlsl(void *sl_handle,
+			   unsigned char addr,
+			   unsigned int cmd,
+			   struct mpu_read_write __user *usr_msg)
+{
+	int retval = 0;
+	struct mpu_read_write msg;
+	unsigned char *user_data;
+	retval = copy_from_user(&msg, usr_msg, sizeof(msg));
+	if (retval)
+		return -EFAULT;
+
+	user_data = msg.data;
+	if (msg.length && msg.data) {
+		unsigned char *data;
+		data = kmalloc(msg.length, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+
+		retval = copy_from_user(data,
+					(void __user *)msg.data, msg.length);
+		if (retval) {
+			retval = -EFAULT;
+			kfree(data);
+			return retval;
+		}
+		msg.data = data;
+	} else {
+		return -EPERM;
+	}
+
+	switch (cmd) {
+	case MPU_READ:
+		retval = inv_serial_read(sl_handle, addr,
+					 msg.address, msg.length, msg.data);
+		break;
+	case MPU_WRITE:
+		retval = inv_serial_write(sl_handle, addr,
+					  msg.length, msg.data);
+		break;
+	case MPU_READ_MEM:
+		retval = inv_serial_read_mem(sl_handle, addr,
+					     msg.address, msg.length, msg.data);
+		break;
+	case MPU_WRITE_MEM:
+		retval = inv_serial_write_mem(sl_handle, addr,
+					      msg.address, msg.length,
+					      msg.data);
+		break;
+	case MPU_READ_FIFO:
+		retval = inv_serial_read_fifo(sl_handle, addr,
+					      msg.length, msg.data);
+		break;
+	case MPU_WRITE_FIFO:
+		retval = inv_serial_write_fifo(sl_handle, addr,
+					       msg.length, msg.data);
+		break;
+
+	};
+	if (retval) {
+		dev_err(&((struct i2c_adapter *)sl_handle)->dev,
+			"%s: i2c %d error %d\n",
+			__func__, cmd, retval);
+		kfree(msg.data);
+		return retval;
+	}
+	retval = copy_to_user((unsigned char __user *)user_data,
+			      msg.data, msg.length);
+	kfree(msg.data);
+	return retval;
+}
+
+/* ioctl - I/O control */
+static long mpu_dev_ioctl(struct file *file,
+			  unsigned int cmd, unsigned long arg)
+{
+	struct mpu_private_data *mpu =
+	    container_of(file->private_data, struct mpu_private_data, dev);
+	struct i2c_client *client = mpu->client;
+	struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg;
+	int retval = 0;
+	struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES];
+	struct ext_slave_descr **slave = mldl_cfg->slave;
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+	int ii;
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!pdata_slave[ii])
+			slave_adapter[ii] = NULL;
+		else
+			slave_adapter[ii] =
+				i2c_get_adapter(pdata_slave[ii]->adapt_num);
+	}
+	slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter;
+
+	retval = mutex_lock_interruptible(&mpu->mutex);
+	if (retval) {
+		dev_err(&client->adapter->dev,
+			"%s: mutex_lock_interruptible returned %d\n",
+			__func__, retval);
+		return retval;
+	}
+
+	switch (cmd) {
+	case MPU_GET_EXT_SLAVE_PLATFORM_DATA:
+		retval = mpu_dev_ioctl_get_ext_slave_platform_data(
+			client,
+			(struct ext_slave_platform_data __user *)arg);
+		break;
+	case MPU_GET_MPU_PLATFORM_DATA:
+		retval = mpu_dev_ioctl_get_mpu_platform_data(
+			client,
+			(struct mpu_platform_data __user *)arg);
+		break;
+	case MPU_GET_EXT_SLAVE_DESCR:
+		retval = mpu_dev_ioctl_get_ext_slave_descr(
+			client,
+			(struct ext_slave_descr __user *)arg);
+		break;
+	case MPU_READ:
+	case MPU_WRITE:
+	case MPU_READ_MEM:
+	case MPU_WRITE_MEM:
+	case MPU_READ_FIFO:
+	case MPU_WRITE_FIFO:
+		retval = mpu_handle_mlsl(
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			mldl_cfg->mpu_chip_info->addr, cmd,
+			(struct mpu_read_write __user *)arg);
+		break;
+	case MPU_CONFIG_GYRO:
+		retval = inv_mpu_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_CONFIG_ACCEL:
+		retval = slave_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+			slave[EXT_SLAVE_TYPE_ACCEL],
+			pdata_slave[EXT_SLAVE_TYPE_ACCEL],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_CONFIG_COMPASS:
+		retval = slave_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+			slave[EXT_SLAVE_TYPE_COMPASS],
+			pdata_slave[EXT_SLAVE_TYPE_COMPASS],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_CONFIG_PRESSURE:
+		retval = slave_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+			slave[EXT_SLAVE_TYPE_PRESSURE],
+			pdata_slave[EXT_SLAVE_TYPE_PRESSURE],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_GET_CONFIG_GYRO:
+		retval = inv_mpu_get_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_GET_CONFIG_ACCEL:
+		retval = slave_get_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+			slave[EXT_SLAVE_TYPE_ACCEL],
+			pdata_slave[EXT_SLAVE_TYPE_ACCEL],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_GET_CONFIG_COMPASS:
+		retval = slave_get_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+			slave[EXT_SLAVE_TYPE_COMPASS],
+			pdata_slave[EXT_SLAVE_TYPE_COMPASS],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_GET_CONFIG_PRESSURE:
+		retval = slave_get_config(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+			slave[EXT_SLAVE_TYPE_PRESSURE],
+			pdata_slave[EXT_SLAVE_TYPE_PRESSURE],
+			(struct ext_slave_config __user *)arg);
+		break;
+	case MPU_SUSPEND:
+		retval = inv_mpu_suspend(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+			slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+			slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+			arg);
+		break;
+	case MPU_RESUME:
+		retval = inv_mpu_resume(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+			slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+			slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+			arg);
+		break;
+	case MPU_PM_EVENT_HANDLED:
+		dev_dbg(&client->adapter->dev, "%s: %d\n", __func__, cmd);
+		complete(&mpu->completion);
+		break;
+	case MPU_READ_ACCEL:
+		retval = inv_slave_read(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+			slave[EXT_SLAVE_TYPE_ACCEL],
+			pdata_slave[EXT_SLAVE_TYPE_ACCEL],
+			(unsigned char __user *)arg);
+		break;
+	case MPU_READ_COMPASS:
+		retval = inv_slave_read(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+			slave[EXT_SLAVE_TYPE_COMPASS],
+			pdata_slave[EXT_SLAVE_TYPE_COMPASS],
+			(unsigned char __user *)arg);
+		break;
+	case MPU_READ_PRESSURE:
+		retval = inv_slave_read(
+			mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+			slave[EXT_SLAVE_TYPE_PRESSURE],
+			pdata_slave[EXT_SLAVE_TYPE_PRESSURE],
+			(unsigned char __user *)arg);
+		break;
+	case MPU_GET_REQUESTED_SENSORS:
+		if (copy_to_user(
+			   (__u32 __user *)arg,
+			   &mldl_cfg->inv_mpu_cfg->requested_sensors,
+			   sizeof(mldl_cfg->inv_mpu_cfg->requested_sensors)))
+			retval = -EFAULT;
+		break;
+	case MPU_SET_REQUESTED_SENSORS:
+		mldl_cfg->inv_mpu_cfg->requested_sensors = arg;
+		break;
+	case MPU_GET_IGNORE_SYSTEM_SUSPEND:
+		if (copy_to_user(
+			(unsigned char __user *)arg,
+			&mldl_cfg->inv_mpu_cfg->ignore_system_suspend,
+			sizeof(mldl_cfg->inv_mpu_cfg->ignore_system_suspend)))
+			retval = -EFAULT;
+		break;
+	case MPU_SET_IGNORE_SYSTEM_SUSPEND:
+		mldl_cfg->inv_mpu_cfg->ignore_system_suspend = arg;
+		break;
+	case MPU_GET_MLDL_STATUS:
+		if (copy_to_user(
+			(unsigned char __user *)arg,
+			&mldl_cfg->inv_mpu_state->status,
+			sizeof(mldl_cfg->inv_mpu_state->status)))
+			retval = -EFAULT;
+		break;
+	case MPU_GET_I2C_SLAVES_ENABLED:
+		if (copy_to_user(
+			(unsigned char __user *)arg,
+			&mldl_cfg->inv_mpu_state->i2c_slaves_enabled,
+			sizeof(mldl_cfg->inv_mpu_state->i2c_slaves_enabled)))
+			retval = -EFAULT;
+		break;
+	default:
+		dev_err(&client->adapter->dev,
+			"%s: Unknown cmd %x, arg %lu\n",
+			__func__, cmd, arg);
+		retval = -EINVAL;
+	};
+
+	mutex_unlock(&mpu->mutex);
+	dev_dbg(&client->adapter->dev, "%s: %08x, %08lx, %d\n",
+		__func__, cmd, arg, retval);
+
+	if (retval > 0)
+		retval = -retval;
+
+	return retval;
+}
+
+void mpu_shutdown(struct i2c_client *client)
+{
+	struct mpu_private_data *mpu =
+	    (struct mpu_private_data *)i2c_get_clientdata(client);
+	struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg;
+	struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES];
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+	int ii;
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!pdata_slave[ii])
+			slave_adapter[ii] = NULL;
+		else
+			slave_adapter[ii] =
+				i2c_get_adapter(pdata_slave[ii]->adapt_num);
+	}
+	slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter;
+
+	mutex_lock(&mpu->mutex);
+	(void)inv_mpu_suspend(mldl_cfg,
+			slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+			slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+			slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+			slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+			INV_ALL_SENSORS);
+	mutex_unlock(&mpu->mutex);
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+}
+
+int mpu_dev_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct mpu_private_data *mpu =
+	    (struct mpu_private_data *)i2c_get_clientdata(client);
+	struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg;
+	struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES];
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+	int ii;
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!pdata_slave[ii])
+			slave_adapter[ii] = NULL;
+		else
+			slave_adapter[ii] =
+				i2c_get_adapter(pdata_slave[ii]->adapt_num);
+	}
+	slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter;
+
+	mutex_lock(&mpu->mutex);
+	if (!mldl_cfg->inv_mpu_cfg->ignore_system_suspend) {
+		dev_dbg(&client->adapter->dev,
+			"%s: suspending on event %d\n", __func__, mesg.event);
+		(void)inv_mpu_suspend(mldl_cfg,
+				slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+				slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+				slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+				slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+				INV_ALL_SENSORS);
+	} else {
+		dev_dbg(&client->adapter->dev,
+			"%s: Already suspended %d\n", __func__, mesg.event);
+	}
+	mutex_unlock(&mpu->mutex);
+	return 0;
+}
+
+int mpu_dev_resume(struct i2c_client *client)
+{
+	struct mpu_private_data *mpu =
+	    (struct mpu_private_data *)i2c_get_clientdata(client);
+	struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg;
+	struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES];
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+	int ii;
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!pdata_slave[ii])
+			slave_adapter[ii] = NULL;
+		else
+			slave_adapter[ii] =
+				i2c_get_adapter(pdata_slave[ii]->adapt_num);
+	}
+	slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter;
+
+	mutex_lock(&mpu->mutex);
+	if (mpu->pid && !mldl_cfg->inv_mpu_cfg->ignore_system_suspend) {
+		(void)inv_mpu_resume(mldl_cfg,
+				slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+				slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+				slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+				slave_adapter[EXT_SLAVE_TYPE_PRESSURE],
+				mldl_cfg->inv_mpu_cfg->requested_sensors);
+		dev_dbg(&client->adapter->dev,
+			"%s for pid %d\n", __func__, mpu->pid);
+	}
+	mutex_unlock(&mpu->mutex);
+	return 0;
+}
+
+/* define which file operations are supported */
+static const struct file_operations mpu_fops = {
+	.owner = THIS_MODULE,
+	.read = mpu_read,
+	.poll = mpu_poll,
+	.unlocked_ioctl = mpu_dev_ioctl,
+	.open = mpu_dev_open,
+	.release = mpu_release,
+};
+
+int inv_mpu_register_slave(struct module *slave_module,
+			struct i2c_client *slave_client,
+			struct ext_slave_platform_data *slave_pdata,
+			struct ext_slave_descr *(*get_slave_descr)(void))
+{
+	struct mpu_private_data *mpu = mpu_private_data;
+	struct mldl_cfg *mldl_cfg;
+	struct ext_slave_descr *slave_descr;
+	struct ext_slave_platform_data **pdata_slave;
+	char *irq_name;
+	int result = 0;
+
+	if (!slave_client || !slave_pdata || !get_slave_descr)
+		return -EINVAL;
+
+	if (!mpu) {
+		dev_err(&slave_client->adapter->dev,
+			"%s: Null mpu_private_data\n", __func__);
+		return -EINVAL;
+	}
+	mldl_cfg = &mpu->mldl_cfg;
+	pdata_slave = mldl_cfg->pdata_slave;
+
+	slave_descr = get_slave_descr();
+	if (!slave_descr) {
+		dev_err(&slave_client->adapter->dev,
+			"%s: Null ext_slave_descr\n", __func__);
+		return -EINVAL;
+	}
+
+	mutex_lock(&mpu->mutex);
+	if (mpu->pid) {
+		mutex_unlock(&mpu->mutex);
+		return -EBUSY;
+	}
+
+	mpu->slave_modules[slave_descr->type] = slave_module;
+
+	if (pdata_slave[slave_descr->type]) {
+		result = -EBUSY;
+		goto out_unlock_mutex;
+	}
+
+	pdata_slave[slave_descr->type] = slave_pdata;
+	pdata_slave[slave_descr->type]->address		= slave_client->addr;
+	pdata_slave[slave_descr->type]->irq		= slave_client->irq;
+	pdata_slave[slave_descr->type]->adapt_num	=
+		i2c_adapter_id(slave_client->adapter);
+
+	switch (slave_descr->type) {
+	case EXT_SLAVE_TYPE_ACCEL:
+		irq_name = "accelirq";
+		break;
+	case EXT_SLAVE_TYPE_COMPASS:
+		irq_name = "compassirq";
+		break;
+	case EXT_SLAVE_TYPE_PRESSURE:
+		irq_name = "pressureirq";
+		break;
+	default:
+		irq_name = "none";
+	};
+
+	if (pdata_slave[slave_descr->type]->irq > 0) {
+		dev_info(&slave_client->adapter->dev,
+			"Installing %s irq using %d\n",
+			irq_name,
+			pdata_slave[slave_descr->type]->irq);
+		result = slaveirq_init(slave_client->adapter,
+				pdata_slave[slave_descr->type], irq_name);
+		if (result)
+			goto out_unlock_mutex;
+	} else {
+		dev_WARN(&slave_client->adapter->dev,
+			"Accel irq not assigned\n");
+	}
+
+	if (slave_descr->init) {
+		result = slave_descr->init(slave_client->adapter,
+					slave_descr,
+					pdata_slave[slave_descr->type]);
+		if (result) {
+			dev_err(&slave_client->adapter->dev,
+				"Accel init failed %d\n", result);
+			goto out_slaveirq_exit;
+		}
+	}
+
+	mldl_cfg->slave[slave_descr->type] = slave_descr;
+
+	dev_info(&slave_client->adapter->dev,
+		"%s: +%s Type %d: Addr: %2x IRQ: %2d, Adapt: %2d\n",
+		__func__,
+		mldl_cfg->slave[slave_descr->type]->name,
+		slave_descr->type,
+		pdata_slave[slave_descr->type]->address,
+		pdata_slave[slave_descr->type]->irq,
+		pdata_slave[slave_descr->type]->adapt_num);
+	goto out_unlock_mutex;
+
+out_slaveirq_exit:
+	if (pdata_slave[slave_descr->type]->irq > 0)
+		slaveirq_exit(pdata_slave[slave_descr->type]);
+out_unlock_mutex:
+	mutex_unlock(&mpu->mutex);
+	return result;
+}
+EXPORT_SYMBOL(inv_mpu_register_slave);
+
+void inv_mpu_unregister_slave(struct i2c_client *slave_client,
+			struct ext_slave_platform_data *slave_pdata,
+			struct ext_slave_descr *(*get_slave_descr)(void))
+{
+	struct mpu_private_data *mpu = mpu_private_data;
+	struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg;
+	struct ext_slave_descr *slave_descr;
+	int result;
+
+	dev_info(&slave_client->adapter->dev, "%s\n", __func__);
+
+	if (!slave_client || !slave_pdata || !get_slave_descr)
+		return;
+
+	slave_descr = get_slave_descr();
+	if (!slave_descr)
+		return;
+
+	mutex_lock(&mpu->mutex);
+
+	if (slave_descr->exit) {
+		result = slave_descr->exit(slave_client->adapter,
+					slave_descr,
+					slave_pdata);
+		if (result)
+			dev_err(&slave_client->adapter->dev,
+				"Accel exit failed %d\n", result);
+	}
+
+	if (slave_pdata->irq)
+		slaveirq_exit(slave_pdata);
+
+	mldl_cfg->slave[slave_descr->type] = NULL;
+	mldl_cfg->pdata_slave[slave_descr->type] = NULL;
+	mpu->slave_modules[slave_descr->type] = NULL;
+
+	mutex_unlock(&mpu->mutex);
+}
+EXPORT_SYMBOL(inv_mpu_unregister_slave);
+
+static unsigned short normal_i2c[] = { I2C_CLIENT_END };
+
+static const struct i2c_device_id mpu_id[] = {
+	{"mpu3050", 0},
+	{"mpu6050", 0},
+	{"mpu6050_no_accel", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, mpu_id);
+
+int mpu_probe(struct i2c_client *client, const struct i2c_device_id *devid)
+{
+	struct mpu_platform_data *pdata;
+	struct mpu_private_data *mpu;
+	struct mldl_cfg *mldl_cfg;
+	int res = 0;
+	int ii = 0;
+
+	dev_info(&client->adapter->dev, "%s: %d\n", __func__, ii++);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		res = -ENODEV;
+		goto out_check_functionality_failed;
+	}
+
+	mpu = kzalloc(sizeof(struct mpu_private_data), GFP_KERNEL);
+	if (!mpu) {
+		res = -ENOMEM;
+		goto out_alloc_data_failed;
+	}
+	mldl_cfg = &mpu->mldl_cfg;
+	mldl_cfg->mpu_ram	= &mpu->mpu_ram;
+	mldl_cfg->mpu_gyro_cfg	= &mpu->mpu_gyro_cfg;
+	mldl_cfg->mpu_offsets	= &mpu->mpu_offsets;
+	mldl_cfg->mpu_chip_info	= &mpu->mpu_chip_info;
+	mldl_cfg->inv_mpu_cfg	= &mpu->inv_mpu_cfg;
+	mldl_cfg->inv_mpu_state	= &mpu->inv_mpu_state;
+
+	mldl_cfg->mpu_ram->length = MPU_MEM_NUM_RAM_BANKS * MPU_MEM_BANK_SIZE;
+	mldl_cfg->mpu_ram->ram = kzalloc(mldl_cfg->mpu_ram->length, GFP_KERNEL);
+	if (!mldl_cfg->mpu_ram->ram) {
+		res = -ENOMEM;
+		goto out_alloc_ram_failed;
+	}
+	mpu_private_data = mpu;
+	i2c_set_clientdata(client, mpu);
+	mpu->client = client;
+
+	init_waitqueue_head(&mpu->mpu_event_wait);
+	mutex_init(&mpu->mutex);
+	init_completion(&mpu->completion);
+
+	mpu->response_timeout = 60;	/* Seconds */
+	mpu->timeout.function = mpu_pm_timeout;
+	mpu->timeout.data = (u_long) mpu;
+	init_timer(&mpu->timeout);
+
+	mpu->nb.notifier_call = mpu_pm_notifier_callback;
+	mpu->nb.priority = 0;
+	res = register_pm_notifier(&mpu->nb);
+	if (res) {
+		dev_err(&client->adapter->dev,
+			"Unable to register pm_notifier %d\n", res);
+		goto out_register_pm_notifier_failed;
+	}
+
+	pdata = (struct mpu_platform_data *)client->dev.platform_data;
+	if (!pdata) {
+		dev_WARN(&client->adapter->dev,
+			 "Missing platform data for mpu\n");
+	}
+	mldl_cfg->pdata = pdata;
+
+	mldl_cfg->mpu_chip_info->addr = client->addr;
+	res = inv_mpu_open(&mpu->mldl_cfg, client->adapter, NULL, NULL, NULL);
+
+	if (res) {
+		dev_err(&client->adapter->dev,
+			"Unable to open %s %d\n", MPU_NAME, res);
+		res = -ENODEV;
+		goto out_whoami_failed;
+	}
+
+	mpu->dev.minor = MISC_DYNAMIC_MINOR;
+	mpu->dev.name = "mpu";
+	mpu->dev.fops = &mpu_fops;
+	res = misc_register(&mpu->dev);
+	if (res < 0) {
+		dev_err(&client->adapter->dev,
+			"ERROR: misc_register returned %d\n", res);
+		goto out_misc_register_failed;
+	}
+
+	if (client->irq) {
+		dev_info(&client->adapter->dev,
+			 "Installing irq using %d\n", client->irq);
+		res = mpuirq_init(client, mldl_cfg);
+		if (res)
+			goto out_mpuirq_failed;
+	} else {
+		dev_WARN(&client->adapter->dev,
+			 "Missing %s IRQ\n", MPU_NAME);
+	}
+
+	return res;
+
+out_mpuirq_failed:
+	misc_deregister(&mpu->dev);
+out_misc_register_failed:
+	inv_mpu_close(&mpu->mldl_cfg, client->adapter, NULL, NULL, NULL);
+out_whoami_failed:
+	unregister_pm_notifier(&mpu->nb);
+out_register_pm_notifier_failed:
+	kfree(mldl_cfg->mpu_ram->ram);
+	mpu_private_data = NULL;
+out_alloc_ram_failed:
+	kfree(mpu);
+out_alloc_data_failed:
+out_check_functionality_failed:
+	dev_err(&client->adapter->dev, "%s failed %d\n", __func__, res);
+	return res;
+
+}
+
+static int mpu_remove(struct i2c_client *client)
+{
+	struct mpu_private_data *mpu = i2c_get_clientdata(client);
+	struct i2c_adapter *slave_adapter[EXT_SLAVE_NUM_TYPES];
+	struct mldl_cfg *mldl_cfg = &mpu->mldl_cfg;
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+	int ii;
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!pdata_slave[ii])
+			slave_adapter[ii] = NULL;
+		else
+			slave_adapter[ii] =
+				i2c_get_adapter(pdata_slave[ii]->adapt_num);
+	}
+
+	slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE] = client->adapter;
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+
+	inv_mpu_close(mldl_cfg,
+		slave_adapter[EXT_SLAVE_TYPE_GYROSCOPE],
+		slave_adapter[EXT_SLAVE_TYPE_ACCEL],
+		slave_adapter[EXT_SLAVE_TYPE_COMPASS],
+		slave_adapter[EXT_SLAVE_TYPE_PRESSURE]);
+
+	if (client->irq)
+		mpuirq_exit();
+
+	for (ii = EXT_SLAVE_TYPE_ACCEL - 1; ii >= 0; ii--) {
+		if (pdata_slave[ii] &&
+		    pdata_slave[ii]->irq) {
+			slaveirq_exit(pdata_slave[ii]);
+		}
+	}
+
+	unregister_pm_notifier(&mpu->nb);
+
+	misc_deregister(&mpu->dev);
+	kfree(mpu);
+
+	return 0;
+}
+
+static struct i2c_driver mpu_driver = {
+	.class = I2C_CLASS_HWMON,
+	.probe = mpu_probe,
+	.remove = mpu_remove,
+	.id_table = mpu_id,
+	.driver = {
+		   .owner = THIS_MODULE,
+		   .name = MPU_NAME,
+		   },
+	.address_list = normal_i2c,
+	.shutdown = mpu_shutdown,	/* optional */
+	.suspend = mpu_dev_suspend,	/* optional */
+	.resume = mpu_dev_resume,	/* optional */
+
+};
+
+static int __init mpu_init(void)
+{
+	int res = i2c_add_driver(&mpu_driver);
+	pr_info("%s: Probe name %s\n", __func__, MPU_NAME);
+	if (res)
+		pr_err("%s failed\n", __func__);
+	return res;
+}
+
+static void __exit mpu_exit(void)
+{
+	pr_info("%s\n", __func__);
+	i2c_del_driver(&mpu_driver);
+}
+
+module_init(mpu_init);
+module_exit(mpu_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("User space character device interface for MPU");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS(MPU_NAME);
diff --git a/drivers/misc/inv_mpu/mpu-dev.h b/drivers/misc/inv_mpu/mpu-dev.h
new file mode 100644
index 0000000..b6a4fcf
--- /dev/null
+++ b/drivers/misc/inv_mpu/mpu-dev.h
@@ -0,0 +1,36 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+
+#ifndef __MPU_DEV_H__
+#define __MPU_DEV_H__
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mpu.h>
+
+int inv_mpu_register_slave(struct module *slave_module,
+			struct i2c_client *client,
+			struct ext_slave_platform_data *pdata,
+			struct ext_slave_descr *(*slave_descr)(void));
+
+void inv_mpu_unregister_slave(struct i2c_client *client,
+			struct ext_slave_platform_data *pdata,
+			struct ext_slave_descr *(*slave_descr)(void));
+#endif
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 04/11] misc: IRQ handling for MPU3050 and slave devices
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
  2011-07-01  2:18 ` [PATCH 02/11] misc: mpu3050 Register definition and Private data Nathan Royer
  2011-07-01  2:18 ` [PATCH 03/11] misc: mpu3050 /dev/mpu implementation Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 05/11] misc: MPU3050 and slave device configuration Nathan Royer
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/mpuirq.c   |  256 +++++++++++++++++++++++++++++++++++++
 drivers/misc/inv_mpu/mpuirq.h   |   36 ++++++
 drivers/misc/inv_mpu/slaveirq.c |  265 +++++++++++++++++++++++++++++++++++++++
 drivers/misc/inv_mpu/slaveirq.h |   38 ++++++
 4 files changed, 595 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/mpuirq.c
 create mode 100644 drivers/misc/inv_mpu/mpuirq.h
 create mode 100644 drivers/misc/inv_mpu/slaveirq.c
 create mode 100644 drivers/misc/inv_mpu/slaveirq.h

diff --git a/drivers/misc/inv_mpu/mpuirq.c b/drivers/misc/inv_mpu/mpuirq.c
new file mode 100644
index 0000000..ffe0e05
--- /dev/null
+++ b/drivers/misc/inv_mpu/mpuirq.c
@@ -0,0 +1,256 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+#include <linux/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/poll.h>
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include <linux/mpu.h>
+#include "mpuirq.h"
+#include "mldl_cfg.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 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 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;
+	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 &&
+	    mpuirq_dev_data.timeout && (!(file->f_flags & O_NONBLOCK))) {
+		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));
+		mpuirq_data.data_type = 0;
+	} 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;
+
+	poll_wait(file, &mpuirq_wait, poll);
+	if (mpuirq_dev_data.data_ready)
+		mask |= POLLIN | POLLRDNORM;
+	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 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 */
+	mpuirq_dev_data.data_ready = 1;
+
+	do_gettimeofday(&irqtime);
+	mpuirq_data.irqtime = (((long long)irqtime.tv_sec) << 32);
+	mpuirq_data.irqtime += irqtime.tv_usec;
+	mpuirq_data.data_type = MPUIRQ_DATA_TYPE_MPU_IRQ;
+	mpuirq_data.data = 0;
+
+	wake_up_interruptible(&mpuirq_wait);
+
+	return IRQ_HANDLED;
+
+}
+
+/* define which file operations are supported */
+const struct file_operations mpuirq_fops = {
+	.owner = THIS_MODULE,
+	.read = mpuirq_read,
+	.poll = mpuirq_poll,
+
+	.unlocked_ioctl = mpuirq_ioctl,
+	.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, struct mldl_cfg *mldl_cfg)
+{
+
+	int res;
+
+	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)
+{
+	if (mpuirq_dev_data.irq > 0)
+		free_irq(mpuirq_dev_data.irq, &mpuirq_dev_data.irq);
+
+	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/inv_mpu/mpuirq.h b/drivers/misc/inv_mpu/mpuirq.h
new file mode 100644
index 0000000..3348071
--- /dev/null
+++ b/drivers/misc/inv_mpu/mpuirq.h
@@ -0,0 +1,36 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+#ifndef __MPUIRQ__
+#define __MPUIRQ__
+
+#include <linux/i2c-dev.h>
+#include <linux/time.h>
+#include <linux/ioctl.h>
+#include "mldl_cfg.h"
+
+#define MPUIRQ_SET_TIMEOUT           _IOW(MPU_IOCTL, 0x40, unsigned long)
+#define MPUIRQ_GET_INTERRUPT_CNT     _IOR(MPU_IOCTL, 0x41, unsigned long)
+#define MPUIRQ_GET_IRQ_TIME          _IOR(MPU_IOCTL, 0x42, struct timeval)
+#define MPUIRQ_SET_FREQUENCY_DIVIDER _IOW(MPU_IOCTL, 0x43, unsigned long)
+
+void mpuirq_exit(void);
+int mpuirq_init(struct i2c_client *mpu_client, struct mldl_cfg *mldl_cfg);
+
+#endif
diff --git a/drivers/misc/inv_mpu/slaveirq.c b/drivers/misc/inv_mpu/slaveirq.c
new file mode 100644
index 0000000..9044d06
--- /dev/null
+++ b/drivers/misc/inv_mpu/slaveirq.c
@@ -0,0 +1,265 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+#include <linux/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/poll.h>
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+
+#include <linux/mpu.h>
+#include "slaveirq.h"
+#include "mldl_cfg.h"
+
+/* function which gets slave data and sends it to SLAVE */
+
+struct slaveirq_dev_data {
+	struct miscdevice dev;
+	struct i2c_client *slave_client;
+	struct mpuirq_data data;
+	wait_queue_head_t slaveirq_wait;
+	int irq;
+	int pid;
+	int data_ready;
+	int timeout;
+};
+
+/* The following depends on patch fa1f68db6ca7ebb6fc4487ac215bffba06c01c28
+ * drivers: misc: pass miscdevice pointer via file private data
+ */
+static int slaveirq_open(struct inode *inode, struct file *file)
+{
+	/* Device node is availabe in the file->private_data, this is
+	 * exactly what we want so we leave it there */
+	struct slaveirq_dev_data *data =
+	    container_of(file->private_data, struct slaveirq_dev_data, dev);
+
+	dev_dbg(data->dev.this_device,
+		"%s current->pid %d\n", __func__, current->pid);
+	data->pid = current->pid;
+	return 0;
+}
+
+static int slaveirq_release(struct inode *inode, struct file *file)
+{
+	struct slaveirq_dev_data *data =
+	    container_of(file->private_data, struct slaveirq_dev_data, dev);
+	dev_dbg(data->dev.this_device, "slaveirq_release\n");
+	return 0;
+}
+
+/* read function called when from /dev/slaveirq is read */
+static ssize_t slaveirq_read(struct file *file,
+			     char *buf, size_t count, loff_t *ppos)
+{
+	int len, err;
+	struct slaveirq_dev_data *data =
+	    container_of(file->private_data, struct slaveirq_dev_data, dev);
+
+	if (!data->data_ready && data->timeout &&
+	    !(file->f_flags & O_NONBLOCK)) {
+		wait_event_interruptible_timeout(data->slaveirq_wait,
+						 data->data_ready,
+						 data->timeout);
+	}
+
+	if (data->data_ready && NULL != buf && count >= sizeof(data->data)) {
+		err = copy_to_user(buf, &data->data, sizeof(data->data));
+		data->data.data_type = 0;
+	} else {
+		return 0;
+	}
+	if (err != 0) {
+		dev_err(data->dev.this_device,
+			"Copy to user returned %d\n", err);
+		return -EFAULT;
+	}
+	data->data_ready = 0;
+	len = sizeof(data->data);
+	return len;
+}
+
+static unsigned int slaveirq_poll(struct file *file,
+				  struct poll_table_struct *poll)
+{
+	int mask = 0;
+	struct slaveirq_dev_data *data =
+	    container_of(file->private_data, struct slaveirq_dev_data, dev);
+
+	poll_wait(file, &data->slaveirq_wait, poll);
+	if (data->data_ready)
+		mask |= POLLIN | POLLRDNORM;
+	return mask;
+}
+
+/* ioctl - I/O control */
+static long slaveirq_ioctl(struct file *file,
+			   unsigned int cmd, unsigned long arg)
+{
+	int retval = 0;
+	int tmp;
+	struct slaveirq_dev_data *data =
+	    container_of(file->private_data, struct slaveirq_dev_data, dev);
+
+	switch (cmd) {
+	case SLAVEIRQ_SET_TIMEOUT:
+		data->timeout = arg;
+		break;
+
+	case SLAVEIRQ_GET_INTERRUPT_CNT:
+		tmp = data->data.interruptcount - 1;
+		if (data->data.interruptcount > 1)
+			data->data.interruptcount = 1;
+
+		if (copy_to_user((int *)arg, &tmp, sizeof(int)))
+			return -EFAULT;
+		break;
+	case SLAVEIRQ_GET_IRQ_TIME:
+		if (copy_to_user((int *)arg, &data->data.irqtime,
+				 sizeof(data->data.irqtime)))
+			return -EFAULT;
+		data->data.irqtime = 0;
+		break;
+	default:
+		retval = -EINVAL;
+	}
+	return retval;
+}
+
+static irqreturn_t slaveirq_handler(int irq, void *dev_id)
+{
+	struct slaveirq_dev_data *data = (struct slaveirq_dev_data *)dev_id;
+	static int mycount;
+	struct timeval irqtime;
+	mycount++;
+
+	data->data.interruptcount++;
+
+	/* wake up (unblock) for reading data from userspace */
+	data->data_ready = 1;
+
+	do_gettimeofday(&irqtime);
+	data->data.irqtime = (((long long)irqtime.tv_sec) << 32);
+	data->data.irqtime += irqtime.tv_usec;
+	data->data.data_type |= 1;
+
+	wake_up_interruptible(&data->slaveirq_wait);
+
+	return IRQ_HANDLED;
+
+}
+
+/* define which file operations are supported */
+static const struct file_operations slaveirq_fops = {
+	.owner = THIS_MODULE,
+	.read = slaveirq_read,
+	.poll = slaveirq_poll,
+
+#if HAVE_COMPAT_IOCTL
+	.compat_ioctl = slaveirq_ioctl,
+#endif
+#if HAVE_UNLOCKED_IOCTL
+	.unlocked_ioctl = slaveirq_ioctl,
+#endif
+	.open = slaveirq_open,
+	.release = slaveirq_release,
+};
+
+int slaveirq_init(struct i2c_adapter *slave_adapter,
+		  struct ext_slave_platform_data *pdata, char *name)
+{
+
+	int res;
+	struct slaveirq_dev_data *data;
+
+	if (!pdata->irq)
+		return -EINVAL;
+
+	pdata->irq_data = kzalloc(sizeof(*data), GFP_KERNEL);
+	data = (struct slaveirq_dev_data *)pdata->irq_data;
+	if (!data)
+		return -ENOMEM;
+
+	data->dev.minor = MISC_DYNAMIC_MINOR;
+	data->dev.name = name;
+	data->dev.fops = &slaveirq_fops;
+	data->irq = pdata->irq;
+	data->pid = 0;
+	data->data_ready = 0;
+	data->timeout = 0;
+
+	init_waitqueue_head(&data->slaveirq_wait);
+
+	res = request_irq(data->irq, slaveirq_handler, IRQF_TRIGGER_RISING,
+			  data->dev.name, data);
+
+	if (res) {
+		dev_err(&slave_adapter->dev,
+			"myirqtest: cannot register IRQ %d\n", data->irq);
+		goto out_request_irq;
+	}
+
+	res = misc_register(&data->dev);
+	if (res < 0) {
+		dev_err(&slave_adapter->dev,
+			"misc_register returned %d\n", res);
+		goto out_misc_register;
+	}
+
+	return res;
+
+out_misc_register:
+	free_irq(data->irq, data);
+out_request_irq:
+	kfree(pdata->irq_data);
+	pdata->irq_data = NULL;
+
+	return res;
+}
+
+void slaveirq_exit(struct ext_slave_platform_data *pdata)
+{
+	struct slaveirq_dev_data *data = pdata->irq_data;
+
+	if (!pdata->irq_data || data->irq <= 0)
+		return;
+
+	dev_info(data->dev.this_device, "Unregistering %s\n", data->dev.name);
+
+	free_irq(data->irq, data);
+	misc_deregister(&data->dev);
+	kfree(pdata->irq_data);
+	pdata->irq_data = NULL;
+}
diff --git a/drivers/misc/inv_mpu/slaveirq.h b/drivers/misc/inv_mpu/slaveirq.h
new file mode 100644
index 0000000..fe05b6f
--- /dev/null
+++ b/drivers/misc/inv_mpu/slaveirq.h
@@ -0,0 +1,38 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+#ifndef __SLAVEIRQ__
+#define __SLAVEIRQ__
+
+#include <linux/i2c-dev.h>
+
+#include <linux/mpu.h>
+#include "mpuirq.h"
+
+#define SLAVEIRQ_SET_TIMEOUT           _IOW(MPU_IOCTL, 0x50, unsigned long)
+#define SLAVEIRQ_GET_INTERRUPT_CNT     _IOR(MPU_IOCTL, 0x51, unsigned long)
+#define SLAVEIRQ_GET_IRQ_TIME          _IOR(MPU_IOCTL, 0x52, unsigned long)
+
+
+void slaveirq_exit(struct ext_slave_platform_data *pdata);
+int slaveirq_init(struct i2c_adapter *slave_adapter,
+		  struct ext_slave_platform_data *pdata, char *name);
+
+
+#endif
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 05/11] misc: MPU3050 and slave device configuration.
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (2 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 04/11] misc: IRQ handling for MPU3050 and slave devices Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01 17:55   ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 06/11] misc: inv_mpu logging and debugging support Nathan Royer
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/mldl_cfg.c | 1737 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 1737 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/mldl_cfg.c

diff --git a/drivers/misc/inv_mpu/mldl_cfg.c b/drivers/misc/inv_mpu/mldl_cfg.c
new file mode 100644
index 0000000..55a772d
--- /dev/null
+++ b/drivers/misc/inv_mpu/mldl_cfg.c
@@ -0,0 +1,1737 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ *  @addtogroup MLDL
+ *
+ *  @{
+ *      @file   mldl_cfg.c
+ *      @brief  The Motion Library Driver Layer.
+ */
+
+/* -------------------------------------------------------------------------- */
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <stddef.h>
+
+#include "mldl_cfg.h"
+#include <linux/mpu.h>
+#  include "mpu3050.h"
+
+#include "mlsl.h"
+#include "mldl_print_cfg.h"
+#include "log.h"
+#undef MPL_LOG_TAG
+#define MPL_LOG_TAG "mldl_cfg:"
+
+/* -------------------------------------------------------------------------- */
+
+#define SLEEP   1
+#define WAKE_UP 0
+#define RESET   1
+#define STANDBY 1
+
+/* -------------------------------------------------------------------------- */
+
+/**
+ * @brief Stop the DMP running
+ *
+ * @return INV_SUCCESS or non-zero error code
+ */
+static int dmp_stop(struct mldl_cfg *mldl_cfg, void *gyro_handle)
+{
+	unsigned char user_ctrl_reg;
+	int result;
+
+	if (mldl_cfg->inv_mpu_state->status & MPU_DMP_IS_SUSPENDED)
+		return INV_SUCCESS;
+
+	result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_USER_CTRL, 1, &user_ctrl_reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	user_ctrl_reg = (user_ctrl_reg & (~BIT_FIFO_EN)) | BIT_FIFO_RST;
+	user_ctrl_reg = (user_ctrl_reg & (~BIT_DMP_EN)) | BIT_DMP_RST;
+
+	result = inv_serial_single_write(gyro_handle,
+					 mldl_cfg->mpu_chip_info->addr,
+					 MPUREG_USER_CTRL, user_ctrl_reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	mldl_cfg->inv_mpu_state->status |= MPU_DMP_IS_SUSPENDED;
+
+	return result;
+}
+
+/**
+ * @brief Starts the DMP running
+ *
+ * @return INV_SUCCESS or non-zero error code
+ */
+static int dmp_start(struct mldl_cfg *mldl_cfg, void *mlsl_handle)
+{
+	unsigned char user_ctrl_reg;
+	int result;
+
+	if ((!(mldl_cfg->inv_mpu_state->status & MPU_DMP_IS_SUSPENDED) &&
+	    mldl_cfg->mpu_gyro_cfg->dmp_enable)
+		||
+	   ((mldl_cfg->inv_mpu_state->status & MPU_DMP_IS_SUSPENDED) &&
+		   !mldl_cfg->mpu_gyro_cfg->dmp_enable))
+		return INV_SUCCESS;
+
+	result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_USER_CTRL, 1, &user_ctrl_reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = inv_serial_single_write(
+		mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_USER_CTRL,
+		((user_ctrl_reg & (~BIT_FIFO_EN))
+			| BIT_FIFO_RST));
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = inv_serial_single_write(
+		mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_USER_CTRL, user_ctrl_reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_USER_CTRL, 1, &user_ctrl_reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	user_ctrl_reg |= BIT_DMP_EN;
+
+	if (mldl_cfg->mpu_gyro_cfg->fifo_enable)
+		user_ctrl_reg |= BIT_FIFO_EN;
+	else
+		user_ctrl_reg &= ~BIT_FIFO_EN;
+
+	user_ctrl_reg |= BIT_DMP_RST;
+
+	result = inv_serial_single_write(
+		mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_USER_CTRL, user_ctrl_reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	mldl_cfg->inv_mpu_state->status &= ~MPU_DMP_IS_SUSPENDED;
+
+	return result;
+}
+
+
+
+static int mpu3050_set_i2c_bypass(struct mldl_cfg *mldl_cfg,
+				  void *mlsl_handle, unsigned char enable)
+{
+	unsigned char b;
+	int result;
+	unsigned char status = mldl_cfg->inv_mpu_state->status;
+
+	if ((status & MPU_GYRO_IS_BYPASSED && enable) ||
+	    (!(status & MPU_GYRO_IS_BYPASSED) && !enable))
+		return INV_SUCCESS;
+
+	/*---- get current 'USER_CTRL' into b ----*/
+	result = inv_serial_read(
+		mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_USER_CTRL, 1, &b);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	b &= ~BIT_AUX_IF_EN;
+
+	if (!enable) {
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_USER_CTRL,
+			(b | BIT_AUX_IF_EN));
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	} else {
+		/* Coming out of I2C is tricky due to several erratta.  Do not
+		 * modify this algorithm
+		 */
+		/*
+		 * 1) wait for the right time and send the command to change
+		 * the aux i2c slave address to an invalid address that will
+		 * get nack'ed
+		 *
+		 * 0x00 is broadcast.  0x7F is unlikely to be used by any aux.
+		 */
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_AUX_SLV_ADDR, 0x7F);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		/*
+		 * 2) wait enough time for a nack to occur, then go into
+		 *    bypass mode:
+		 */
+		msleep(2);
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_USER_CTRL, (b));
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		/*
+		 * 3) wait for up to one MPU cycle then restore the slave
+		 *    address
+		 */
+		msleep(inv_mpu_get_sampling_period_us(mldl_cfg->mpu_gyro_cfg)
+			/ 1000);
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_AUX_SLV_ADDR,
+			mldl_cfg->pdata_slave[EXT_SLAVE_TYPE_ACCEL]
+				->address);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+
+		/*
+		 * 4) reset the ime interface
+		 */
+
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_USER_CTRL,
+			(b | BIT_AUX_IF_RST));
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		msleep(2);
+	}
+	if (enable)
+		mldl_cfg->inv_mpu_state->status |= MPU_GYRO_IS_BYPASSED;
+	else
+		mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_IS_BYPASSED;
+
+	return result;
+}
+
+
+/**
+ *  @brief  enables/disables the I2C bypass to an external device
+ *          connected to MPU's secondary I2C bus.
+ *  @param  enable
+ *              Non-zero to enable pass through.
+ *  @return INV_SUCCESS if successful, a non-zero error code otherwise.
+ */
+static int mpu_set_i2c_bypass(struct mldl_cfg *mldl_cfg,
+			      void *mlsl_handle, unsigned char enable)
+{
+	return mpu3050_set_i2c_bypass(mldl_cfg, mlsl_handle, enable);
+}
+
+
+#define NUM_OF_PROD_REVS (ARRAY_SIZE(prod_rev_map))
+
+/* NOTE : when not indicated, product revision
+	  is considered an 'npp'; non production part */
+
+struct prod_rev_map_t {
+	unsigned char silicon_rev;
+	unsigned short gyro_trim;
+};
+
+#define OLDEST_PROD_REV_SUPPORTED	11
+static struct prod_rev_map_t prod_rev_map[] = {
+	{0, 0},
+	{MPU_SILICON_REV_A4, 131},	/* 1  A? OBSOLETED */
+	{MPU_SILICON_REV_A4, 131},	/* 2  |  */
+	{MPU_SILICON_REV_A4, 131},	/* 3  |  */
+	{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 V  */
+	{MPU_SILICON_REV_B1, 131},	/* 11 B1 */
+	{MPU_SILICON_REV_B1, 131},	/* 12 |  */
+	{MPU_SILICON_REV_B1, 131},	/* 13 |  */
+	{MPU_SILICON_REV_B1, 131},	/* 14 V  */
+	{MPU_SILICON_REV_B4, 131},	/* 15 B4 */
+	{MPU_SILICON_REV_B4, 131},	/* 16 |  */
+	{MPU_SILICON_REV_B4, 131},	/* 17 |  */
+	{MPU_SILICON_REV_B4, 131},	/* 18 |  */
+	{MPU_SILICON_REV_B4, 115},	/* 19 |  */
+	{MPU_SILICON_REV_B4, 115},	/* 20 V  */
+	{MPU_SILICON_REV_B6, 131},	/* 21 B6 (B6/A9)  */
+	{MPU_SILICON_REV_B4, 115},	/* 22 B4 (B7/A10) */
+	{MPU_SILICON_REV_B6, 0},	/* 23 B6 */
+	{MPU_SILICON_REV_B6, 0},	/* 24 |  */
+	{MPU_SILICON_REV_B6, 0},	/* 25 |  */
+	{MPU_SILICON_REV_B6, 131},	/* 26 V  (B6/A11) */
+};
+
+/**
+ *  @internal
+ *  @brief  Get the silicon revision ID from OTP for MPU3050.
+ *          The silicon revision number is in read from OTP bank 0,
+ *          ADDR6[7:2].  The corresponding ID is retrieved by lookup
+ *          in a map.
+ *
+ *  @param  mldl_cfg
+ *              a pointer to the mldl config data structure.
+ *  @param  mlsl_handle
+ *              an file handle to the serial communication device the
+ *              device is connected to.
+ *
+ *  @return 0 on success, a non-zero error code otherwise.
+ */
+static int inv_get_silicon_rev_mpu3050(
+		struct mldl_cfg *mldl_cfg, void *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 mem_addr = ((bank << 8) | 0x06);
+	struct mpu_chip_info *mpu_chip_info = mldl_cfg->mpu_chip_info;
+
+	result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_PRODUCT_ID, 1,
+				 &mpu_chip_info->product_id);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = inv_serial_read_mem(
+		mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+		mem_addr, 1, &index);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	index >>= 2;
+
+	/* clean the prefetch and cfg user bank bits */
+	result = inv_serial_single_write(
+		mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_BANK_SEL, 0);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	if (index < OLDEST_PROD_REV_SUPPORTED || index >= NUM_OF_PROD_REVS) {
+		mpu_chip_info->silicon_revision = 0;
+		mpu_chip_info->gyro_sens_trim = 0;
+		MPL_LOGE("Unsupported Product Revision Detected : %d\n", index);
+		return INV_ERROR_INVALID_MODULE;
+	}
+
+	mpu_chip_info->product_revision = index;
+	mpu_chip_info->silicon_revision = prod_rev_map[index].silicon_rev;
+	mpu_chip_info->gyro_sens_trim = prod_rev_map[index].gyro_trim;
+	if (mpu_chip_info->gyro_sens_trim == 0) {
+		MPL_LOGE("gyro sensitivity trim is 0"
+			 " - unsupported non production part.\n");
+		return INV_ERROR_INVALID_MODULE;
+	}
+
+	return result;
+}
+#define inv_get_silicon_rev inv_get_silicon_rev_mpu3050
+
+
+/**
+ *  @brief  Enable / Disable the use MPU's secondary I2C interface level
+ *          shifters.
+ *          When enabled the secondary I2C interface to which the external
+ *          device is connected runs at VDD voltage (main supply).
+ *          When disabled the 2nd interface runs at VDDIO voltage.
+ *          See the device specification for more details.
+ *
+ *  @note   using this API may produce unpredictable results, depending on how
+ *          the MPU and slave device are setup on the target platform.
+ *          Use of this API should entirely be restricted to system
+ *          integrators. Once the correct value is found, there should be no
+ *          need to change the level shifter at runtime.
+ *
+ *  @pre    Must be called after inv_serial_start().
+ *  @note   Typically called before inv_dmp_open().
+ *
+ *  @param[in]  enable:
+ *                  0 to run at VDDIO (default),
+ *                  1 to run at VDD.
+ *
+ *  @return INV_SUCCESS if successfull, a non-zero error code otherwise.
+ */
+static int inv_mpu_set_level_shifter_bit(struct mldl_cfg *mldl_cfg,
+				  void *mlsl_handle, unsigned char enable)
+{
+	int result;
+	unsigned char reg;
+	unsigned char mask;
+	unsigned char regval;
+
+	if (0 == mldl_cfg->mpu_chip_info->silicon_revision)
+		return INV_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 ((mldl_cfg->mpu_chip_info->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 = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				 reg, 1, &regval);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	if (enable)
+		regval |= mask;
+	else
+		regval &= ~mask;
+
+	result = inv_serial_single_write(
+		mlsl_handle, mldl_cfg->mpu_chip_info->addr, reg, regval);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	return result;
+	return INV_SUCCESS;
+}
+
+
+/**
+ * @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 being applied.
+ * @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  INV_SUCCESS if successfull; a non-zero error code otherwise.
+ */
+static int mpu3050_pwr_mgmt(struct mldl_cfg *mldl_cfg,
+			    void *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 =
+	    inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			    MPUREG_PWR_MGM, 1, &b);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* If we are awake, we need to put it in bypass before resetting */
+	if ((!(b & BIT_SLEEP)) && reset)
+		result = mpu_set_i2c_bypass(mldl_cfg, mlsl_handle, 1);
+
+	/* Reset if requested */
+	if (reset) {
+		MPL_LOGV("Reset MPU3050\n");
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_PWR_MGM, b | BIT_H_RESET);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		msleep(5);
+		mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_NEEDS_RESET;
+		/* Some chips are awake after reset and some are asleep,
+		 * check the status */
+		result = inv_serial_read(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_PWR_MGM, 1, &b);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	/* Update the suspended state just in case we return early */
+	if (b & BIT_SLEEP)
+		mldl_cfg->inv_mpu_state->status |= MPU_GYRO_IS_SUSPENDED;
+	else
+		mldl_cfg->inv_mpu_state->status &= ~MPU_GYRO_IS_SUSPENDED;
+
+	/* if power status match requested, nothing else's left to do */
+	if ((b & (BIT_SLEEP | BIT_STBY_XG | BIT_STBY_YG | BIT_STBY_ZG)) ==
+	    (((sleep != 0) * BIT_SLEEP) |
+	     ((disable_gx != 0) * BIT_STBY_XG) |
+	     ((disable_gy != 0) * BIT_STBY_YG) |
+	     ((disable_gz != 0) * BIT_STBY_ZG))) {
+		return INV_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) is (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)
+	    && ((!sleep) && disable_gx && disable_gy && disable_gz)) {
+		result = mpu3050_pwr_mgmt(mldl_cfg, 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 != 0) * BIT_SLEEP)) {
+		if (sleep) {
+			result = mpu_set_i2c_bypass(mldl_cfg, mlsl_handle, 1);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+			b |= BIT_SLEEP;
+			result =
+			    inv_serial_single_write(
+				    mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				    MPUREG_PWR_MGM, b);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+			mldl_cfg->inv_mpu_state->status |=
+				MPU_GYRO_IS_SUSPENDED;
+		} else {
+			b &= ~BIT_SLEEP;
+			result =
+			    inv_serial_single_write(
+				    mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				    MPUREG_PWR_MGM, b);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+			mldl_cfg->inv_mpu_state->status &=
+				~MPU_GYRO_IS_SUSPENDED;
+			msleep(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 != 0) * BIT_STBY_XG)) {
+		b ^= BIT_STBY_XG;
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_PWR_MGM, b);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+	if ((b & BIT_STBY_YG) != ((disable_gy != 0) * BIT_STBY_YG)) {
+		b ^= BIT_STBY_YG;
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_PWR_MGM, b);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+	if ((b & BIT_STBY_ZG) != ((disable_gz != 0) * BIT_STBY_ZG)) {
+		b ^= BIT_STBY_ZG;
+		result = inv_serial_single_write(
+			mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+			MPUREG_PWR_MGM, b);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	return INV_SUCCESS;
+}
+
+
+/**
+ *  @brief  sets the clock source for the gyros.
+ *  @param  mldl_cfg
+ *              a pointer to the struct mldl_cfg data structure.
+ *  @param  gyro_handle
+ *              an handle to the serial device the gyro is assigned to.
+ *  @return ML_SUCCESS if successful, a non-zero error code otherwise.
+ */
+static int mpu_set_clock_source(void *gyro_handle, struct mldl_cfg *mldl_cfg)
+{
+	int result;
+	unsigned char cur_clk_src;
+	unsigned char reg;
+
+	/* clock source selection */
+	result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_PWR_MGM, 1, &reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	cur_clk_src = reg & BITS_CLKSEL;
+	reg &= ~BITS_CLKSEL;
+
+
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_PWR_MGM, mldl_cfg->mpu_gyro_cfg->clk_src | reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* TODO : workarounds to be determined and implemented */
+
+	return result;
+}
+
+/**
+ * Configures the MPU I2C Master
+ *
+ * @mldl_cfg Handle to the configuration data
+ * @gyro_handle handle to the gyro communictation interface
+ * @slave Can be Null if turning off the slave
+ * @slave_pdata Can be null if turning off the slave
+ * @slave_id enum ext_slave_type to determine which index to use
+ *
+ *
+ * This fucntion configures the slaves by:
+ * 1) Setting up the read
+ *    a) Read Register
+ *    b) Read Length
+ * 2) Set up the data trigger (MPU6050 only)
+ *    a) Set trigger write register
+ *    b) Set Trigger write value
+ * 3) Set up the divider (MPU6050 only)
+ * 4) Set the slave bypass mode depending on slave
+ *
+ * returns INV_SUCCESS or non-zero error code
+ */
+static int mpu_set_slave_mpu3050(struct mldl_cfg *mldl_cfg,
+				 void *gyro_handle,
+				 struct ext_slave_descr *slave,
+				 struct ext_slave_platform_data *slave_pdata,
+				 int slave_id)
+{
+	int result;
+	unsigned char reg;
+	unsigned char slave_reg;
+	unsigned char slave_len;
+	unsigned char slave_endian;
+	unsigned char slave_address;
+
+	if (slave_id != EXT_SLAVE_TYPE_ACCEL)
+		return 0;
+
+	result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, true);
+
+	if (NULL == slave || NULL == slave_pdata) {
+		slave_reg = 0;
+		slave_len = 0;
+		slave_endian = 0;
+		slave_address = 0;
+		mldl_cfg->inv_mpu_state->i2c_slaves_enabled = 0;
+	} else {
+		slave_reg = slave->read_reg;
+		slave_len = slave->read_len;
+		slave_endian = slave->endian;
+		slave_address = slave_pdata->address;
+		mldl_cfg->inv_mpu_state->i2c_slaves_enabled = 1;
+	}
+
+	/* Address */
+	result = inv_serial_single_write(gyro_handle,
+					 mldl_cfg->mpu_chip_info->addr,
+					 MPUREG_AUX_SLV_ADDR, slave_address);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* Register */
+	result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_ACCEL_BURST_ADDR, 1, &reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	reg = ((reg & 0x80) | slave_reg);
+	result = inv_serial_single_write(gyro_handle,
+					 mldl_cfg->mpu_chip_info->addr,
+					 MPUREG_ACCEL_BURST_ADDR, reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Length */
+	result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_USER_CTRL, 1, &reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	reg = (reg & ~BIT_AUX_RD_LENG);
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_USER_CTRL, reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	return result;
+}
+
+
+static int mpu_set_slave(struct mldl_cfg *mldl_cfg,
+			 void *gyro_handle,
+			 struct ext_slave_descr *slave,
+			 struct ext_slave_platform_data *slave_pdata,
+			 int slave_id)
+{
+	return mpu_set_slave_mpu3050(mldl_cfg, gyro_handle, slave,
+				     slave_pdata, slave_id);
+}
+/**
+ * Check to see if the gyro was reset by testing a couple of registers known
+ * to change on reset.
+ *
+ * @mldl_cfg mldl configuration structure
+ * @gyro_handle handle used to communicate with the gyro
+ *
+ * @return INV_SUCCESS or non-zero error code
+ */
+static int mpu_was_reset(struct mldl_cfg *mldl_cfg, void *gyro_handle)
+{
+	int result = INV_SUCCESS;
+	unsigned char reg;
+
+	result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_DMP_CFG_2, 1, &reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	if (mldl_cfg->mpu_gyro_cfg->dmp_cfg2 != reg)
+		return true;
+
+	if (0 != mldl_cfg->mpu_gyro_cfg->dmp_cfg1)
+		return false;
+
+	result = inv_serial_read(gyro_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_SMPLRT_DIV, 1, &reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	if (reg != mldl_cfg->mpu_gyro_cfg->divider)
+		return true;
+
+	if (0 != mldl_cfg->mpu_gyro_cfg->divider)
+		return false;
+
+	/* Inconclusive assume it was reset */
+	return true;
+}
+
+static int gyro_resume(struct mldl_cfg *mldl_cfg, void *gyro_handle,
+		       unsigned long sensors)
+{
+	int result;
+	int ii;
+	int jj;
+	unsigned char reg;
+	unsigned char regs[7];
+
+	/* Wake up the part */
+	result = mpu3050_pwr_mgmt(mldl_cfg, gyro_handle, false, false,
+				  !(sensors & INV_X_GYRO),
+				  !(sensors & INV_Y_GYRO),
+				  !(sensors & INV_Z_GYRO));
+
+	if (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_NEEDS_RESET) &&
+	    !mpu_was_reset(mldl_cfg, gyro_handle)) {
+		return INV_SUCCESS;
+	}
+
+	result = mpu3050_pwr_mgmt(mldl_cfg, gyro_handle, true, false,
+				  !(sensors & INV_X_GYRO),
+				  !(sensors & INV_Y_GYRO),
+				  !(sensors & INV_Z_GYRO));
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_INT_CFG,
+		(mldl_cfg->mpu_gyro_cfg->int_config |
+			mldl_cfg->pdata->int_config));
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = mpu_set_clock_source(gyro_handle, mldl_cfg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_SMPLRT_DIV, mldl_cfg->mpu_gyro_cfg->divider);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	reg = DLPF_FS_SYNC_VALUE(mldl_cfg->mpu_gyro_cfg->ext_sync,
+				 mldl_cfg->mpu_gyro_cfg->full_scale,
+				 mldl_cfg->mpu_gyro_cfg->lpf);
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_DLPF_FS_SYNC, reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_DMP_CFG_1, mldl_cfg->mpu_gyro_cfg->dmp_cfg1);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_DMP_CFG_2, mldl_cfg->mpu_gyro_cfg->dmp_cfg2);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Write and verify memory */
+	for (ii = 0; ii < MPU_MEM_NUM_RAM_BANKS; ii++) {
+		unsigned char read[MPU_MEM_BANK_SIZE];
+
+		result = inv_serial_write_mem(
+			gyro_handle, mldl_cfg->mpu_chip_info->addr,
+			((ii << 8) | 0x00), MPU_MEM_BANK_SIZE,
+			&mldl_cfg->mpu_ram->ram[ii * MPU_MEM_BANK_SIZE]);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		result = inv_serial_read_mem(
+			gyro_handle, mldl_cfg->mpu_chip_info->addr,
+			((ii << 8) | 0x00), MPU_MEM_BANK_SIZE, read);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+
+#define ML_SKIP_CHECK 20
+		for (jj = 0; jj < MPU_MEM_BANK_SIZE; jj++) {
+			/* skip the register memory locations */
+			if (ii == 0 && jj < ML_SKIP_CHECK)
+				continue;
+			if (mldl_cfg->mpu_ram->ram[ii * MPU_MEM_BANK_SIZE + jj]
+				!= read[jj]) {
+				result = INV_ERROR_SERIAL_WRITE;
+				break;
+			}
+		}
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_XG_OFFS_TC, mldl_cfg->mpu_offsets->tc[0]);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_YG_OFFS_TC, mldl_cfg->mpu_offsets->tc[1]);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_single_write(
+		gyro_handle, mldl_cfg->mpu_chip_info->addr,
+		MPUREG_ZG_OFFS_TC, mldl_cfg->mpu_offsets->tc[2]);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	regs[0] = MPUREG_X_OFFS_USRH;
+	for (ii = 0; ii < ARRAY_SIZE(mldl_cfg->mpu_offsets->gyro); ii++) {
+		regs[1 + ii * 2] =
+			(unsigned char)(mldl_cfg->mpu_offsets->gyro[ii] >> 8)
+			& 0xff;
+		regs[1 + ii * 2 + 1] =
+			(unsigned char)(mldl_cfg->mpu_offsets->gyro[ii] & 0xff);
+	}
+	result = inv_serial_write(gyro_handle, mldl_cfg->mpu_chip_info->addr,
+				  7, regs);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Configure slaves */
+	result = inv_mpu_set_level_shifter_bit(mldl_cfg, gyro_handle,
+					       mldl_cfg->pdata->level_shifter);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	return result;
+}
+
+int gyro_config(void *mlsl_handle,
+		struct mldl_cfg *mldl_cfg,
+		struct ext_slave_config *data)
+{
+	struct mpu_gyro_cfg *mpu_gyro_cfg = mldl_cfg->mpu_gyro_cfg;
+	struct mpu_chip_info *mpu_chip_info = mldl_cfg->mpu_chip_info;
+	struct mpu_offsets *mpu_offsets = mldl_cfg->mpu_offsets;
+	int ii;
+
+	if (!data->data)
+		return INV_ERROR_INVALID_PARAMETER;
+
+	switch (data->key) {
+	case MPU_SLAVE_INT_CONFIG:
+		mpu_gyro_cfg->int_config = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_EXT_SYNC:
+		mpu_gyro_cfg->ext_sync = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_FULL_SCALE:
+		mpu_gyro_cfg->full_scale = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_LPF:
+		mpu_gyro_cfg->lpf = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_CLK_SRC:
+		mpu_gyro_cfg->clk_src = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_DIVIDER:
+		mpu_gyro_cfg->divider = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_DMP_ENABLE:
+		mpu_gyro_cfg->dmp_enable = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_FIFO_ENABLE:
+		mpu_gyro_cfg->fifo_enable = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_DMP_CFG1:
+		mpu_gyro_cfg->dmp_cfg1 = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_DMP_CFG2:
+		mpu_gyro_cfg->dmp_cfg2 = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_TC:
+		for (ii = 0; ii < GYRO_NUM_AXES; ii++)
+			mpu_offsets->tc[ii] = ((__u8 *)data->data)[ii];
+		break;
+	case MPU_SLAVE_GYRO:
+		for (ii = 0; ii < GYRO_NUM_AXES; ii++)
+			mpu_offsets->gyro[ii] = ((__u16 *)data->data)[ii];
+		break;
+	case MPU_SLAVE_ADDR:
+		mpu_chip_info->addr = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_PRODUCT_REVISION:
+		mpu_chip_info->product_revision = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_SILICON_REVISION:
+		mpu_chip_info->silicon_revision = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_PRODUCT_ID:
+		mpu_chip_info->product_id = *((__u8 *)data->data);
+		break;
+	case MPU_SLAVE_GYRO_SENS_TRIM:
+		mpu_chip_info->gyro_sens_trim = *((__u16 *)data->data);
+		break;
+	case MPU_SLAVE_ACCEL_SENS_TRIM:
+		mpu_chip_info->accel_sens_trim = *((__u16 *)data->data);
+		break;
+	case MPU_SLAVE_RAM:
+		if (data->len != mldl_cfg->mpu_ram->length)
+			return INV_ERROR_INVALID_PARAMETER;
+
+		memcpy(mldl_cfg->mpu_ram->ram, data->data, data->len);
+		break;
+	default:
+		LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED);
+		return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
+	};
+	mldl_cfg->inv_mpu_state->status |= MPU_GYRO_NEEDS_RESET;
+	return INV_SUCCESS;
+}
+
+int gyro_get_config(void *mlsl_handle,
+		struct mldl_cfg *mldl_cfg,
+		struct ext_slave_config *data)
+{
+	struct mpu_gyro_cfg *mpu_gyro_cfg = mldl_cfg->mpu_gyro_cfg;
+	struct mpu_chip_info *mpu_chip_info = mldl_cfg->mpu_chip_info;
+	struct mpu_offsets *mpu_offsets = mldl_cfg->mpu_offsets;
+	int ii;
+
+	if (!data->data)
+		return INV_ERROR_INVALID_PARAMETER;
+
+	switch (data->key) {
+	case MPU_SLAVE_INT_CONFIG:
+		*((__u8 *)data->data) = mpu_gyro_cfg->int_config;
+		break;
+	case MPU_SLAVE_EXT_SYNC:
+		*((__u8 *)data->data) = mpu_gyro_cfg->ext_sync;
+		break;
+	case MPU_SLAVE_FULL_SCALE:
+		*((__u8 *)data->data) = mpu_gyro_cfg->full_scale;
+		break;
+	case MPU_SLAVE_LPF:
+		*((__u8 *)data->data) = mpu_gyro_cfg->lpf;
+		break;
+	case MPU_SLAVE_CLK_SRC:
+		*((__u8 *)data->data) = mpu_gyro_cfg->clk_src;
+		break;
+	case MPU_SLAVE_DIVIDER:
+		*((__u8 *)data->data) = mpu_gyro_cfg->divider;
+		break;
+	case MPU_SLAVE_DMP_ENABLE:
+		*((__u8 *)data->data) = mpu_gyro_cfg->dmp_enable;
+		break;
+	case MPU_SLAVE_FIFO_ENABLE:
+		*((__u8 *)data->data) = mpu_gyro_cfg->fifo_enable;
+		break;
+	case MPU_SLAVE_DMP_CFG1:
+		*((__u8 *)data->data) = mpu_gyro_cfg->dmp_cfg1;
+		break;
+	case MPU_SLAVE_DMP_CFG2:
+		*((__u8 *)data->data) = mpu_gyro_cfg->dmp_cfg2;
+		break;
+	case MPU_SLAVE_TC:
+		for (ii = 0; ii < GYRO_NUM_AXES; ii++)
+			((__u8 *)data->data)[ii] = mpu_offsets->tc[ii];
+		break;
+	case MPU_SLAVE_GYRO:
+		for (ii = 0; ii < GYRO_NUM_AXES; ii++)
+			((__u16 *)data->data)[ii] = mpu_offsets->gyro[ii];
+		break;
+	case MPU_SLAVE_ADDR:
+		*((__u8 *)data->data) = mpu_chip_info->addr;
+		break;
+	case MPU_SLAVE_PRODUCT_REVISION:
+		*((__u8 *)data->data) = mpu_chip_info->product_revision;
+		break;
+	case MPU_SLAVE_SILICON_REVISION:
+		*((__u8 *)data->data) = mpu_chip_info->silicon_revision;
+		break;
+	case MPU_SLAVE_PRODUCT_ID:
+		*((__u8 *)data->data) = mpu_chip_info->product_id;
+		break;
+	case MPU_SLAVE_GYRO_SENS_TRIM:
+		*((__u16 *)data->data) = mpu_chip_info->gyro_sens_trim;
+		break;
+	case MPU_SLAVE_ACCEL_SENS_TRIM:
+		*((__u16 *)data->data) = mpu_chip_info->accel_sens_trim;
+		break;
+	case MPU_SLAVE_RAM:
+		if (data->len != mldl_cfg->mpu_ram->length)
+			return INV_ERROR_INVALID_PARAMETER;
+
+		memcpy(data->data, mldl_cfg->mpu_ram->ram, data->len);
+		break;
+	default:
+		LOG_RESULT_LOCATION(INV_ERROR_FEATURE_NOT_IMPLEMENTED);
+		return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
+	};
+
+	return INV_SUCCESS;
+}
+
+
+/*******************************************************************************
+ *******************************************************************************
+ * Exported functions
+ *******************************************************************************
+ ******************************************************************************/
+
+/**
+ * Initializes the pdata structure to defaults.
+ *
+ * Opens the device to read silicon revision, product id and whoami.
+ *
+ * @mldl_cfg
+ *          The internal device configuration data structure.
+ * @mlsl_handle
+ *          The serial communication handle.
+ *
+ * @return INV_SUCCESS if silicon revision, product id and woami are supported
+ *         by this software.
+ */
+int inv_mpu_open(struct mldl_cfg *mldl_cfg,
+		 void *mlsl_handle,
+		 void *accel_handle,
+		 void *compass_handle, void *pressure_handle)
+{
+	int result;
+	int ii;
+
+	/* Default is Logic HIGH, pushpull, latch disabled, anyread to clear */
+	mldl_cfg->inv_mpu_cfg->ignore_system_suspend = false;
+	mldl_cfg->mpu_gyro_cfg->int_config = BIT_DMP_INT_EN;
+	mldl_cfg->mpu_gyro_cfg->clk_src = MPU_CLK_SEL_PLLGYROZ;
+	mldl_cfg->mpu_gyro_cfg->lpf = MPU_FILTER_42HZ;
+	mldl_cfg->mpu_gyro_cfg->full_scale = MPU_FS_2000DPS;
+	mldl_cfg->mpu_gyro_cfg->divider = 4;
+	mldl_cfg->mpu_gyro_cfg->dmp_enable = 1;
+	mldl_cfg->mpu_gyro_cfg->fifo_enable = 1;
+	mldl_cfg->mpu_gyro_cfg->ext_sync = 0;
+	mldl_cfg->mpu_gyro_cfg->dmp_cfg1 = 0;
+	mldl_cfg->mpu_gyro_cfg->dmp_cfg2 = 0;
+	mldl_cfg->inv_mpu_state->status =
+		MPU_DMP_IS_SUSPENDED |
+		MPU_GYRO_IS_SUSPENDED |
+		MPU_ACCEL_IS_SUSPENDED |
+		MPU_COMPASS_IS_SUSPENDED |
+		MPU_PRESSURE_IS_SUSPENDED;
+	mldl_cfg->inv_mpu_state->i2c_slaves_enabled = 0;
+
+	if (mldl_cfg->mpu_chip_info->addr == 0) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+
+	/*
+	 * Reset,
+	 * Take the DMP out of sleep, and
+	 * read the product_id, sillicon rev and whoami
+	 */
+	mldl_cfg->inv_mpu_state->status |= MPU_GYRO_IS_BYPASSED;
+	result = mpu3050_pwr_mgmt(mldl_cfg, mlsl_handle, RESET, 0, 0, 0, 0);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = inv_get_silicon_rev(mldl_cfg, mlsl_handle);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Get the factory temperature compensation offsets */
+	result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_XG_OFFS_TC, 1,
+				 &mldl_cfg->mpu_offsets->tc[0]);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_YG_OFFS_TC, 1,
+				 &mldl_cfg->mpu_offsets->tc[1]);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_read(mlsl_handle, mldl_cfg->mpu_chip_info->addr,
+				 MPUREG_ZG_OFFS_TC, 1,
+				 &mldl_cfg->mpu_offsets->tc[2]);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Into bypass mode before sleeping and calling the slaves init */
+	result = mpu_set_i2c_bypass(mldl_cfg, mlsl_handle, true);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = mpu3050_pwr_mgmt(mldl_cfg, mlsl_handle, 0, SLEEP, 0, 0, 0);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+
+		if (!mldl_cfg->slave[ii] || !mldl_cfg->slave[ii]->init)
+			continue;
+
+		result = mldl_cfg->slave[ii]->init(accel_handle,
+						mldl_cfg->slave[ii],
+						mldl_cfg->pdata_slave[ii]);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			goto out_slave_failure;
+		}
+
+	}
+
+
+	return result;
+
+out_slave_failure:
+	for (ii = ii - 1; ii >= 0; ii--)
+		if (mldl_cfg->slave[ii] && mldl_cfg->slave[ii]->exit)
+			mldl_cfg->slave[ii]->exit(compass_handle,
+						mldl_cfg->slave[ii],
+						mldl_cfg->pdata_slave[ii]);
+	return result;
+}
+
+/**
+ * Close the mpu interface
+ *
+ * @mldl_cfg pointer to the configuration structure
+ * @mlsl_handle pointer to the serial layer handle
+ *
+ * @return INV_SUCCESS or non-zero error code
+ */
+int inv_mpu_close(struct mldl_cfg *mldl_cfg,
+		  void *mlsl_handle,
+		  void *accel_handle,
+		  void *compass_handle,
+		  void *pressure_handle)
+{
+	int result = INV_SUCCESS;
+	int ret_result = INV_SUCCESS;
+	int ii;
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!mldl_cfg->slave[ii] || !mldl_cfg->slave[ii]->exit)
+			continue;
+		result = mldl_cfg->slave[ii]->exit(accel_handle,
+						mldl_cfg->slave[ii],
+						mldl_cfg->pdata_slave[ii]);
+		if (INV_SUCCESS != result)
+			MPL_LOGE("Slave %d exit failed %d\n", ii, result);
+
+		if (INV_SUCCESS == ret_result)
+			ret_result = result;
+	}
+
+	return ret_result;
+}
+
+/**
+ *  @brief  resume the MPU device and all the other sensor
+ *          devices from their low power state.
+ *
+ *  @mldl_cfg
+ *              pointer to the configuration structure
+ *  @gyro_handle
+ *              the main file handle to the MPU device.
+ *  @accel_handle
+ *              an handle to the accelerometer device, if sitting
+ *              onto a separate bus. Can match mlsl_handle if
+ *              the accelerometer device operates on the same
+ *              primary bus of MPU.
+ *  @compass_handle
+ *              an handle to the compass device, if sitting
+ *              onto a separate bus. Can match mlsl_handle if
+ *              the compass device operates on the same
+ *              primary bus of MPU.
+ *  @pressure_handle
+ *              an handle to the pressure sensor device, if sitting
+ *              onto a separate bus. Can match mlsl_handle if
+ *              the pressure sensor device operates on the same
+ *              primary bus of MPU.
+ *  @resume_gyro
+ *              whether resuming the gyroscope device is
+ *              actually needed (if the device supports low power
+ *              mode of some sort).
+ *  @resume_accel
+ *              whether resuming the accelerometer device is
+ *              actually needed (if the device supports low power
+ *              mode of some sort).
+ *  @resume_compass
+ *              whether resuming the compass device is
+ *              actually needed (if the device supports low power
+ *              mode of some sort).
+ *  @resume_pressure
+ *              whether resuming the pressure sensor device is
+ *              actually needed (if the device supports low power
+ *              mode of some sort).
+ *  @return  INV_SUCCESS or a non-zero error code.
+ */
+int inv_mpu_resume(struct mldl_cfg *mldl_cfg,
+		   void *gyro_handle,
+		   void *accel_handle,
+		   void *compass_handle,
+		   void *pressure_handle,
+		   unsigned long sensors)
+{
+	int result = INV_SUCCESS;
+	int ii;
+	bool resume_slave[EXT_SLAVE_NUM_TYPES];
+	bool resume_dmp = sensors & INV_DMP_PROCESSOR;
+	void *slave_handle[EXT_SLAVE_NUM_TYPES];
+	resume_slave[EXT_SLAVE_TYPE_GYROSCOPE] =
+		(sensors & (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO));
+	resume_slave[EXT_SLAVE_TYPE_ACCEL] =
+		sensors & INV_THREE_AXIS_ACCEL;
+	resume_slave[EXT_SLAVE_TYPE_COMPASS] =
+		sensors & INV_THREE_AXIS_COMPASS;
+	resume_slave[EXT_SLAVE_TYPE_PRESSURE] =
+		sensors & INV_THREE_AXIS_PRESSURE;
+
+	slave_handle[EXT_SLAVE_TYPE_GYROSCOPE] = gyro_handle;
+	slave_handle[EXT_SLAVE_TYPE_ACCEL] = accel_handle;
+	slave_handle[EXT_SLAVE_TYPE_COMPASS] = compass_handle;
+	slave_handle[EXT_SLAVE_TYPE_PRESSURE] = pressure_handle;
+
+
+	mldl_print_cfg(mldl_cfg);
+
+	/* Skip the Gyro since slave[EXT_SLAVE_TYPE_GYROSCOPE] is NULL */
+	for (ii = EXT_SLAVE_TYPE_ACCEL; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (resume_slave[ii] &&
+		    ((!mldl_cfg->slave[ii]) ||
+			(!mldl_cfg->slave[ii]->resume))) {
+			LOG_RESULT_LOCATION(INV_ERROR_INVALID_PARAMETER);
+			return INV_ERROR_INVALID_PARAMETER;
+		}
+	}
+
+	if ((resume_slave[EXT_SLAVE_TYPE_GYROSCOPE] || resume_dmp)
+		&& (mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_SUSPENDED)) {
+		result = gyro_resume(mldl_cfg, gyro_handle, sensors);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!mldl_cfg->slave[ii] ||
+		    !mldl_cfg->pdata_slave[ii] ||
+		    !resume_slave[ii] ||
+		    !(mldl_cfg->inv_mpu_state->status & (1 << ii)))
+			continue;
+
+		if (EXT_SLAVE_BUS_SECONDARY ==
+		    mldl_cfg->pdata_slave[ii]->bus) {
+			result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle,
+						    true);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+		}
+		result = mldl_cfg->slave[ii]->resume(slave_handle[ii],
+						mldl_cfg->slave[ii],
+						mldl_cfg->pdata_slave[ii]);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		mldl_cfg->inv_mpu_state->status &= ~(1 << ii);
+	}
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (resume_dmp &&
+		    !(mldl_cfg->inv_mpu_state->status & (1 << ii)) &&
+		    mldl_cfg->pdata_slave[ii] &&
+		    EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata_slave[ii]->bus) {
+			result = mpu_set_slave(mldl_cfg,
+					gyro_handle,
+					mldl_cfg->slave[ii],
+					mldl_cfg->pdata_slave[ii],
+					mldl_cfg->slave[ii]->type);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+		}
+	}
+
+	/* Turn on the master i2c iterface if necessary */
+	if (resume_dmp) {
+		result = mpu_set_i2c_bypass(
+			mldl_cfg, gyro_handle,
+			!(mldl_cfg->inv_mpu_state->i2c_slaves_enabled));
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+
+		/* Now start */
+		result = dmp_start(mldl_cfg, gyro_handle);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+	mldl_cfg->inv_mpu_cfg->requested_sensors = sensors;
+
+	return result;
+}
+
+/**
+ *  @brief  suspend the MPU device and all the other sensor
+ *          devices into their low power state.
+ *  @mldl_cfg
+ *              a pointer to the struct mldl_cfg internal data
+ *              structure.
+ *  @gyro_handle
+ *              the main file handle to the MPU device.
+ *  @accel_handle
+ *              an handle to the accelerometer device, if sitting
+ *              onto a separate bus. Can match gyro_handle if
+ *              the accelerometer device operates on the same
+ *              primary bus of MPU.
+ *  @compass_handle
+ *              an handle to the compass device, if sitting
+ *              onto a separate bus. Can match gyro_handle if
+ *              the compass device operates on the same
+ *              primary bus of MPU.
+ *  @pressure_handle
+ *              an handle to the pressure sensor device, if sitting
+ *              onto a separate bus. Can match gyro_handle if
+ *              the pressure sensor device operates on the same
+ *              primary bus of MPU.
+ *  @accel
+ *              whether suspending the accelerometer device is
+ *              actually needed (if the device supports low power
+ *              mode of some sort).
+ *  @compass
+ *              whether suspending the compass device is
+ *              actually needed (if the device supports low power
+ *              mode of some sort).
+ *  @pressure
+ *              whether suspending the pressure sensor device is
+ *              actually needed (if the device supports low power
+ *              mode of some sort).
+ *  @return  INV_SUCCESS or a non-zero error code.
+ */
+int inv_mpu_suspend(struct mldl_cfg *mldl_cfg,
+		    void *gyro_handle,
+		    void *accel_handle,
+		    void *compass_handle,
+		    void *pressure_handle,
+		    unsigned long sensors)
+{
+	int result = INV_SUCCESS;
+	int ii;
+	struct ext_slave_descr **slave = mldl_cfg->slave;
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+	bool suspend_dmp = ((sensors & INV_DMP_PROCESSOR) == INV_DMP_PROCESSOR);
+	bool suspend_slave[EXT_SLAVE_NUM_TYPES];
+
+	suspend_slave[EXT_SLAVE_TYPE_GYROSCOPE] =
+		((sensors & (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO))
+			== (INV_X_GYRO | INV_Y_GYRO | INV_Z_GYRO));
+	suspend_slave[EXT_SLAVE_TYPE_ACCEL] =
+		((sensors & INV_THREE_AXIS_ACCEL) == INV_THREE_AXIS_ACCEL);
+	suspend_slave[EXT_SLAVE_TYPE_COMPASS] =
+		((sensors & INV_THREE_AXIS_COMPASS) == INV_THREE_AXIS_COMPASS);
+	suspend_slave[EXT_SLAVE_TYPE_PRESSURE] =
+		((sensors & INV_THREE_AXIS_PRESSURE) ==
+			INV_THREE_AXIS_PRESSURE);
+
+	if (suspend_dmp) {
+		result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		result = dmp_stop(mldl_cfg, gyro_handle);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	/* Gyro */
+	if (suspend_slave[EXT_SLAVE_TYPE_GYROSCOPE] &&
+	    !(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_SUSPENDED)) {
+		result = mpu3050_pwr_mgmt(
+			mldl_cfg, gyro_handle, 0,
+			suspend_dmp && suspend_slave[EXT_SLAVE_TYPE_GYROSCOPE],
+			(unsigned char)(sensors & INV_X_GYRO),
+			(unsigned char)(sensors & INV_Y_GYRO),
+			(unsigned char)(sensors & INV_Z_GYRO));
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		bool is_suspended = mldl_cfg->inv_mpu_state->status & (1 << ii);
+		if (!slave[ii]   || !pdata_slave[ii] ||
+		    is_suspended || !suspend_slave[ii])
+			continue;
+
+		if (EXT_SLAVE_BUS_SECONDARY == pdata_slave[ii]->bus) {
+			result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+		}
+		result = slave[ii]->suspend(accel_handle,
+						  slave[ii],
+						  pdata_slave[ii]);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		if (EXT_SLAVE_BUS_SECONDARY == pdata_slave[ii]->bus) {
+			result = mpu_set_slave(mldl_cfg, gyro_handle,
+					       NULL, NULL,
+					       slave[ii]->type);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+		}
+		mldl_cfg->inv_mpu_state->status |= (1 << ii);
+	}
+
+	/* Re-enable the i2c master if there are configured slaves and DMP */
+	if (!suspend_dmp) {
+		result = mpu_set_i2c_bypass(
+			mldl_cfg, gyro_handle,
+			!(mldl_cfg->inv_mpu_state->i2c_slaves_enabled));
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+	mldl_cfg->inv_mpu_cfg->requested_sensors = (~sensors) & INV_ALL_SENSORS;
+
+	return result;
+}
+
+int inv_mpu_slave_read(struct mldl_cfg *mldl_cfg,
+		       void *gyro_handle,
+		       void *slave_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata,
+		       unsigned char *data)
+{
+	int result;
+	int bypass_result;
+	int remain_bypassed = true;
+
+	if (NULL == slave || NULL == slave->read) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION);
+		return INV_ERROR_INVALID_CONFIGURATION;
+	}
+
+	if ((EXT_SLAVE_BUS_SECONDARY == pdata->bus)
+	    && (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_BYPASSED))) {
+		remain_bypassed = false;
+		result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	result = slave->read(slave_handle, slave, pdata, data);
+
+	if (!remain_bypassed) {
+		bypass_result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0);
+		if (bypass_result) {
+			LOG_RESULT_LOCATION(bypass_result);
+			return bypass_result;
+		}
+	}
+	return result;
+}
+
+int inv_mpu_slave_config(struct mldl_cfg *mldl_cfg,
+			 void *gyro_handle,
+			 void *slave_handle,
+			 struct ext_slave_config *data,
+			 struct ext_slave_descr *slave,
+			 struct ext_slave_platform_data *pdata)
+{
+	int result;
+	int remain_bypassed = true;
+
+	if (NULL == slave || NULL == slave->config) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION);
+		return INV_ERROR_INVALID_CONFIGURATION;
+	}
+
+	if (data->apply && (EXT_SLAVE_BUS_SECONDARY == pdata->bus)
+	    && (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_BYPASSED))) {
+		remain_bypassed = false;
+		result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	result = slave->config(slave_handle, slave, pdata, data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	if (!remain_bypassed) {
+		result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+	return result;
+}
+
+int inv_mpu_get_slave_config(struct mldl_cfg *mldl_cfg,
+			     void *gyro_handle,
+			     void *slave_handle,
+			     struct ext_slave_config *data,
+			     struct ext_slave_descr *slave,
+			     struct ext_slave_platform_data *pdata)
+{
+	int result;
+	int remain_bypassed = true;
+
+	if (NULL == slave || NULL == slave->get_config) {
+		LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION);
+		return INV_ERROR_INVALID_CONFIGURATION;
+	}
+
+	if (data->apply && (EXT_SLAVE_BUS_SECONDARY == pdata->bus)
+	    && (!(mldl_cfg->inv_mpu_state->status & MPU_GYRO_IS_BYPASSED))) {
+		remain_bypassed = false;
+		result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 1);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	result = slave->get_config(slave_handle, slave, pdata, data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	if (!remain_bypassed) {
+		result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle, 0);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+	return result;
+}
+
+/**
+ * @}
+ */
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 06/11] misc: inv_mpu logging and debugging support
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (3 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 05/11] misc: MPU3050 and slave device configuration Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 07/11] misc: I2C communication with the MPU3050 and slave devices Nathan Royer
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/log.h            |  287 +++++++++++++++++++++++++++++++++
 drivers/misc/inv_mpu/mldl_print_cfg.c |  131 +++++++++++++++
 drivers/misc/inv_mpu/mldl_print_cfg.h |   38 +++++
 drivers/misc/inv_mpu/mltypes.h        |  226 ++++++++++++++++++++++++++
 4 files changed, 682 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/log.h
 create mode 100644 drivers/misc/inv_mpu/mldl_print_cfg.c
 create mode 100644 drivers/misc/inv_mpu/mldl_print_cfg.h
 create mode 100644 drivers/misc/inv_mpu/mltypes.h

diff --git a/drivers/misc/inv_mpu/log.h b/drivers/misc/inv_mpu/log.h
new file mode 100644
index 0000000..a8db425
--- /dev/null
+++ b/drivers/misc/inv_mpu/log.h
@@ -0,0 +1,287 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/*
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * 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 "mltypes.h"
+#include <stdarg.h>
+
+
+#include <linux/kernel.h>
+
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * 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.
+ */
+#ifndef MPL_LOG_NDEBUG
+#ifdef NDEBUG
+#define MPL_LOG_NDEBUG 1
+#else
+#define MPL_LOG_NDEBUG 0
+#endif
+#endif
+
+#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
+
+
+
+/*
+ * 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
+#define MPL_LOG_TAG
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Simplified macro to send a verbose log message using the current MPL_LOG_TAG.
+ */
+#ifndef MPL_LOGV
+#if MPL_LOG_NDEBUG
+#define MPL_LOGV(fmt, ...)						\
+	do {								\
+		if (0)							\
+			MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, fmt, ##__VA_ARGS__);\
+	} while (0)
+#else
+#define MPL_LOGV(fmt, ...) MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, fmt, ##__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, fmt, ...)  \
+	do { if (0) MPL_LOG(fmt, ##__VA_ARGS__); } while (0)
+#else
+#define MPL_LOGV_IF(cond, fmt, ...) \
+	((CONDITION(cond))						\
+		? MPL_LOG(LOG_VERBOSE, MPL_LOG_TAG, fmt, ##__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(fmt, ...) MPL_LOG(LOG_DEBUG, MPL_LOG_TAG, fmt, ##__VA_ARGS__)
+#endif
+
+#ifndef MPL_LOGD_IF
+#define MPL_LOGD_IF(cond, fmt, ...) \
+	((CONDITION(cond))					       \
+		? MPL_LOG(LOG_DEBUG, MPL_LOG_TAG, fmt, ##__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(fmt, ...) MPL_LOG(LOG_INFO, MPL_LOG_TAG, fmt, ##__VA_ARGS__)
+#endif
+
+#ifndef MPL_LOGI_IF
+#define MPL_LOGI_IF(cond, fmt, ...) \
+	((CONDITION(cond))                                              \
+		? MPL_LOG(LOG_INFO, MPL_LOG_TAG, fmt, ##__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(fmt, ...) printk(KERN_WARNING MPL_LOG_TAG fmt, ##__VA_ARGS__)
+#endif
+
+#ifndef MPL_LOGW_IF
+#define MPL_LOGW_IF(cond, fmt, ...) \
+	((CONDITION(cond))					       \
+		? MPL_LOG(LOG_WARN, MPL_LOG_TAG, fmt, ##__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(fmt, ...) printk(KERN_ERR MPL_LOG_TAG fmt, ##__VA_ARGS__)
+#endif
+
+#ifndef MPL_LOGE_IF
+#define MPL_LOGE_IF(cond, fmt, ...) \
+	((CONDITION(cond))					       \
+		? MPL_LOG(LOG_ERROR, MPL_LOG_TAG, fmt, ##__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, fmt, ...) \
+	((CONDITION(cond))					   \
+		? ((void)android_printAssert(#cond, MPL_LOG_TAG,   \
+						fmt, ##__VA_ARGS__))	\
+		: (void)0)
+
+#define MPL_LOG_ALWAYS_FATAL(fmt, ...) \
+	(((void)android_printAssert(NULL, MPL_LOG_TAG, fmt, ##__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, fmt, ...)				\
+	do {								\
+		if (0)							\
+			MPL_LOG_ALWAYS_FATAL_IF(cond, fmt, ##__VA_ARGS__); \
+	} while (0)
+#define MPL_LOG_FATAL(fmt, ...)						\
+	do {								\
+		if (0)							\
+			MPL_LOG_ALWAYS_FATAL(fmt, ##__VA_ARGS__)	\
+	} while (0)
+#else
+#define MPL_LOG_FATAL_IF(cond, fmt, ...) \
+	MPL_LOG_ALWAYS_FATAL_IF(cond, fmt, ##__VA_ARGS__)
+#define MPL_LOG_FATAL(fmt, ...) \
+	MPL_LOG_ALWAYS_FATAL(fmt, ##__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, fmt, ...)			\
+	MPL_LOG_FATAL_IF(!(cond), fmt, ##__VA_ARGS__)
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * 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, fmt, ...)		\
+	MPL_LOG_PRI(priority, tag, fmt, ##__VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef MPL_LOG_PRI
+#define MPL_LOG_PRI(priority, tag, fmt, ...) \
+	pr_debug(MPL_##priority tag fmt, ##__VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to pass in a varargs ("args" is a va_list).
+ */
+#ifndef MPL_LOG_PRI_VA
+/* not allowed in the Kernel because there is no dev_dbg that takes a va_list */
+#endif
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * ===========================================================================
+ *
+ * The stuff in the rest of this file should not be used directly.
+ */
+
+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);
+
+static inline void __print_result_location(int result,
+					   const char *file,
+					   const char *func, int line)
+{
+	MPL_LOGE("%s|%s|%d returning %d\n", file, func, line, result);
+}
+
+#define LOG_RESULT_LOCATION(condition) \
+	do {								\
+		__print_result_location((int)(condition), __FILE__,	\
+					__func__, __LINE__);		\
+	} while (0)
+
+
+#endif				/* _LIBS_CUTILS_MPL_LOG_H */
diff --git a/drivers/misc/inv_mpu/mldl_print_cfg.c b/drivers/misc/inv_mpu/mldl_print_cfg.c
new file mode 100644
index 0000000..8218824
--- /dev/null
+++ b/drivers/misc/inv_mpu/mldl_print_cfg.c
@@ -0,0 +1,131 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ *  @addtogroup MLDL
+ *
+ *  @{
+ *      @file   mldl_print_cfg.c
+ *      @brief  The Motion Library Driver Layer.
+ */
+
+#include <stddef.h>
+#include "mldl_cfg.h"
+#include "mlsl.h"
+#include "linux/mpu.h"
+
+
+void mldl_print_cfg(struct mldl_cfg *mldl_cfg)
+{
+	struct mpu_gyro_cfg	*mpu_gyro_cfg	= mldl_cfg->mpu_gyro_cfg;
+	struct mpu_offsets	*mpu_offsets	= mldl_cfg->mpu_offsets;
+	struct mpu_chip_info	*mpu_chip_info	= mldl_cfg->mpu_chip_info;
+	struct inv_mpu_cfg	*inv_mpu_cfg	= mldl_cfg->inv_mpu_cfg;
+	struct inv_mpu_state	*inv_mpu_state	= mldl_cfg->inv_mpu_state;
+	struct ext_slave_descr	**slave		= mldl_cfg->slave;
+	struct mpu_platform_data *pdata		= mldl_cfg->pdata;
+	struct ext_slave_platform_data **pdata_slave = mldl_cfg->pdata_slave;
+	int ii;
+
+	/* mpu_gyro_cfg */
+	MPL_LOGD("int_config     = %02x\n", mpu_gyro_cfg->int_config);
+	MPL_LOGD("ext_sync       = %02x\n", mpu_gyro_cfg->ext_sync);
+	MPL_LOGD("full_scale     = %02x\n", mpu_gyro_cfg->full_scale);
+	MPL_LOGD("lpf            = %02x\n", mpu_gyro_cfg->lpf);
+	MPL_LOGD("clk_src        = %02x\n", mpu_gyro_cfg->clk_src);
+	MPL_LOGD("divider        = %02x\n", mpu_gyro_cfg->divider);
+	MPL_LOGD("dmp_enable     = %02x\n", mpu_gyro_cfg->dmp_enable);
+	MPL_LOGD("fifo_enable    = %02x\n", mpu_gyro_cfg->fifo_enable);
+	MPL_LOGD("dmp_cfg1       = %02x\n", mpu_gyro_cfg->dmp_cfg1);
+	MPL_LOGD("dmp_cfg2       = %02x\n", mpu_gyro_cfg->dmp_cfg2);
+	/* mpu_offsets */
+	MPL_LOGD("tc[0]      = %02x\n", mpu_offsets->tc[0]);
+	MPL_LOGD("tc[1]      = %02x\n", mpu_offsets->tc[1]);
+	MPL_LOGD("tc[2]      = %02x\n", mpu_offsets->tc[2]);
+	MPL_LOGD("gyro[0]    = %04x\n", mpu_offsets->gyro[0]);
+	MPL_LOGD("gyro[1]    = %04x\n", mpu_offsets->gyro[1]);
+	MPL_LOGD("gyro[2]    = %04x\n", mpu_offsets->gyro[2]);
+
+	/* mpu_chip_info */
+	MPL_LOGD("addr            = %02x\n", mldl_cfg->mpu_chip_info->addr);
+
+	MPL_LOGD("silicon_revision = %02x\n", mpu_chip_info->silicon_revision);
+	MPL_LOGD("product_revision = %02x\n", mpu_chip_info->product_revision);
+	MPL_LOGD("product_id       = %02x\n", mpu_chip_info->product_id);
+	MPL_LOGD("gyro_sens_trim   = %02x\n", mpu_chip_info->gyro_sens_trim);
+
+	MPL_LOGD("requested_sensors = %04x\n", inv_mpu_cfg->requested_sensors);
+	MPL_LOGD("ignore_system_suspend= %04x\n",
+		inv_mpu_cfg->ignore_system_suspend);
+	MPL_LOGD("status = %04x\n", inv_mpu_state->status);
+	MPL_LOGD("i2c_slaves_enabled= %04x\n",
+		inv_mpu_state->i2c_slaves_enabled);
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!slave[ii])
+			continue;
+		MPL_LOGD("SLAVE %d:\n", ii);
+		MPL_LOGD("    suspend  = %02x\n", (int)slave[ii]->suspend);
+		MPL_LOGD("    resume   = %02x\n", (int)slave[ii]->resume);
+		MPL_LOGD("    read     = %02x\n", (int)slave[ii]->read);
+		MPL_LOGD("    type     = %02x\n", slave[ii]->type);
+		MPL_LOGD("    reg      = %02x\n", slave[ii]->read_reg);
+		MPL_LOGD("    len      = %02x\n", slave[ii]->read_len);
+		MPL_LOGD("    endian   = %02x\n", slave[ii]->endian);
+		MPL_LOGD("    range.mantissa= %02x\n",
+			slave[ii]->range.mantissa);
+		MPL_LOGD("    range.fraction= %02x\n",
+			slave[ii]->range.fraction);
+	}
+
+	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
+		if (!pdata_slave[ii])
+			continue;
+		MPL_LOGD("PDATA_SLAVE[%d]\n", ii);
+		MPL_LOGD("    irq        = %02x\n", pdata_slave[ii]->irq);
+		MPL_LOGD("    adapt_num  = %02x\n", pdata_slave[ii]->adapt_num);
+		MPL_LOGD("    bus        = %02x\n", pdata_slave[ii]->bus);
+		MPL_LOGD("    address    = %02x\n", pdata_slave[ii]->address);
+		MPL_LOGD("    orientation=\n"
+			"                            %2d %2d %2d\n"
+			"                            %2d %2d %2d\n"
+			"                            %2d %2d %2d\n",
+			pdata_slave[ii]->orientation[0],
+			pdata_slave[ii]->orientation[1],
+			pdata_slave[ii]->orientation[2],
+			pdata_slave[ii]->orientation[3],
+			pdata_slave[ii]->orientation[4],
+			pdata_slave[ii]->orientation[5],
+			pdata_slave[ii]->orientation[6],
+			pdata_slave[ii]->orientation[7],
+			pdata_slave[ii]->orientation[8]);
+	}
+
+	MPL_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]);
+}
diff --git a/drivers/misc/inv_mpu/mldl_print_cfg.h b/drivers/misc/inv_mpu/mldl_print_cfg.h
new file mode 100644
index 0000000..2e19114
--- /dev/null
+++ b/drivers/misc/inv_mpu/mldl_print_cfg.h
@@ -0,0 +1,38 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ * @defgroup
+ * @brief
+ *
+ * @{
+ * @file     mldl_print_cfg.h
+ * @brief
+ *
+ *
+ */
+#ifndef __MLDL_PRINT_CFG__
+#define __MLDL_PRINT_CFG__
+
+#include "mldl_cfg.h"
+
+
+void mldl_print_cfg(struct mldl_cfg *mldl_cfg);
+
+#endif /* __MLDL_PRINT_CFG__ */
diff --git a/drivers/misc/inv_mpu/mltypes.h b/drivers/misc/inv_mpu/mltypes.h
new file mode 100644
index 0000000..2abb215
--- /dev/null
+++ b/drivers/misc/inv_mpu/mltypes.h
@@ -0,0 +1,226 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ *  @defgroup MLERROR
+ *  @brief  Motion Library - Error definitions.
+ *          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)       INV_SUCCESS
+ *          - (1)       INV_ERROR
+ *          - (2)       INV_ERROR_INVALID_PARAMETER
+ *          - (3)       INV_ERROR_FEATURE_NOT_ENABLED
+ *          - (4)       INV_ERROR_FEATURE_NOT_IMPLEMENTED
+ *          - (6)       INV_ERROR_DMP_NOT_STARTED
+ *          - (7)       INV_ERROR_DMP_STARTED
+ *          - (8)       INV_ERROR_NOT_OPENED
+ *          - (9)       INV_ERROR_OPENED
+ *          - (10)      INV_ERROR_INVALID_MODULE
+ *          - (11)      INV_ERROR_MEMORY_EXAUSTED
+ *          - (12)      INV_ERROR_DIVIDE_BY_ZERO
+ *          - (13)      INV_ERROR_ASSERTION_FAILURE
+ *          - (14)      INV_ERROR_FILE_OPEN
+ *          - (15)      INV_ERROR_FILE_READ
+ *          - (16)      INV_ERROR_FILE_WRITE
+ *          - (17)      INV_ERROR_INVALID_CONFIGURATION
+ *          - (20)      INV_ERROR_SERIAL_CLOSED
+ *          - (21)      INV_ERROR_SERIAL_OPEN_ERROR
+ *          - (22)      INV_ERROR_SERIAL_READ
+ *          - (23)      INV_ERROR_SERIAL_WRITE
+ *          - (24)      INV_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED
+ *          - (25)      INV_ERROR_SM_TRANSITION
+ *          - (26)      INV_ERROR_SM_IMPROPER_STATE
+ *          - (30)      INV_ERROR_FIFO_OVERFLOW
+ *          - (31)      INV_ERROR_FIFO_FOOTER
+ *          - (32)      INV_ERROR_FIFO_READ_COUNT
+ *          - (33)      INV_ERROR_FIFO_READ_DATA
+ *          - (40)      INV_ERROR_MEMORY_SET
+ *          - (50)      INV_ERROR_LOG_MEMORY_ERROR
+ *          - (51)      INV_ERROR_LOG_OUTPUT_ERROR
+ *          - (60)      INV_ERROR_OS_BAD_PTR
+ *          - (61)      INV_ERROR_OS_BAD_HANDLE
+ *          - (62)      INV_ERROR_OS_CREATE_FAILED
+ *          - (63)      INV_ERROR_OS_LOCK_FAILED
+ *          - (70)      INV_ERROR_COMPASS_DATA_OVERFLOW
+ *          - (71)      INV_ERROR_COMPASS_DATA_UNDERFLOW
+ *          - (72)      INV_ERROR_COMPASS_DATA_NOT_READY
+ *          - (73)      INV_ERROR_COMPASS_DATA_ERROR
+ *          - (75)      INV_ERROR_CALIBRATION_LOAD
+ *          - (76)      INV_ERROR_CALIBRATION_STORE
+ *          - (77)      INV_ERROR_CALIBRATION_LEN
+ *          - (78)      INV_ERROR_CALIBRATION_CHECKSUM
+ *          - (79)      INV_ERROR_ACCEL_DATA_OVERFLOW
+ *          - (80)      INV_ERROR_ACCEL_DATA_UNDERFLOW
+ *          - (81)      INV_ERROR_ACCEL_DATA_NOT_READY
+ *          - (82)      INV_ERROR_ACCEL_DATA_ERROR
+ *
+ *  @{
+ *      @file mltypes.h
+ *  @}
+ */
+
+#ifndef MLTYPES_H
+#define MLTYPES_H
+
+#include <linux/types.h>
+#include <asm-generic/errno-base.h>
+
+
+
+
+/*---------------------------
+ *    ML Defines
+ *--------------------------*/
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+/* - ML Errors. - */
+#define ERROR_NAME(x)   (#x)
+#define ERROR_CHECK_FIRST(first, x)                                     \
+	{ if (INV_SUCCESS == first) first = x; }
+
+#define INV_SUCCESS                       (0)
+/* Generic Error code.  Proprietary Error Codes only */
+#define INV_ERROR_BASE                    (0x20)
+#define INV_ERROR                         (INV_ERROR_BASE)
+
+/* Compatibility and other generic error codes */
+#define INV_ERROR_INVALID_PARAMETER       (EINVAL)
+#define INV_ERROR_FEATURE_NOT_ENABLED     (EPERM)
+#define INV_ERROR_FEATURE_NOT_IMPLEMENTED (INV_ERROR_BASE + 4)
+#define INV_ERROR_DMP_NOT_STARTED         (INV_ERROR_BASE + 6)
+#define INV_ERROR_DMP_STARTED             (INV_ERROR_BASE + 7)
+#define INV_ERROR_NOT_OPENED              (INV_ERROR_BASE + 8)
+#define INV_ERROR_OPENED                  (INV_ERROR_BASE + 9)
+#define INV_ERROR_INVALID_MODULE         (ENODEV)
+#define INV_ERROR_MEMORY_EXAUSTED        (ENOMEM)
+#define INV_ERROR_DIVIDE_BY_ZERO         (INV_ERROR_BASE + 12)
+#define INV_ERROR_ASSERTION_FAILURE      (INV_ERROR_BASE + 13)
+#define INV_ERROR_FILE_OPEN              (INV_ERROR_BASE + 14)
+#define INV_ERROR_FILE_READ              (INV_ERROR_BASE + 15)
+#define INV_ERROR_FILE_WRITE             (INV_ERROR_BASE + 16)
+#define INV_ERROR_INVALID_CONFIGURATION  (INV_ERROR_BASE + 17)
+
+/* Serial Communication */
+#define INV_ERROR_SERIAL_CLOSED          (INV_ERROR_BASE + 20)
+#define INV_ERROR_SERIAL_OPEN_ERROR      (INV_ERROR_BASE + 21)
+#define INV_ERROR_SERIAL_READ            (INV_ERROR_BASE + 22)
+#define INV_ERROR_SERIAL_WRITE           (INV_ERROR_BASE + 23)
+#define INV_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED  (INV_ERROR_BASE + 24)
+
+/* SM = State Machine */
+#define INV_ERROR_SM_TRANSITION          (INV_ERROR_BASE + 25)
+#define INV_ERROR_SM_IMPROPER_STATE      (INV_ERROR_BASE + 26)
+
+/* Fifo */
+#define INV_ERROR_FIFO_OVERFLOW          (INV_ERROR_BASE + 30)
+#define INV_ERROR_FIFO_FOOTER            (INV_ERROR_BASE + 31)
+#define INV_ERROR_FIFO_READ_COUNT        (INV_ERROR_BASE + 32)
+#define INV_ERROR_FIFO_READ_DATA         (INV_ERROR_BASE + 33)
+
+/* Memory & Registers, Set & Get */
+#define INV_ERROR_MEMORY_SET             (INV_ERROR_BASE + 40)
+
+#define INV_ERROR_LOG_MEMORY_ERROR       (INV_ERROR_BASE + 50)
+#define INV_ERROR_LOG_OUTPUT_ERROR       (INV_ERROR_BASE + 51)
+
+/* OS interface errors */
+#define INV_ERROR_OS_BAD_PTR             (INV_ERROR_BASE + 60)
+#define INV_ERROR_OS_BAD_HANDLE          (INV_ERROR_BASE + 61)
+#define INV_ERROR_OS_CREATE_FAILED       (INV_ERROR_BASE + 62)
+#define INV_ERROR_OS_LOCK_FAILED         (INV_ERROR_BASE + 63)
+
+/* Compass errors */
+#define INV_ERROR_COMPASS_DATA_OVERFLOW  (INV_ERROR_BASE + 70)
+#define INV_ERROR_COMPASS_DATA_UNDERFLOW (INV_ERROR_BASE + 71)
+#define INV_ERROR_COMPASS_DATA_NOT_READY (INV_ERROR_BASE + 72)
+#define INV_ERROR_COMPASS_DATA_ERROR     (INV_ERROR_BASE + 73)
+
+/* Load/Store calibration */
+#define INV_ERROR_CALIBRATION_LOAD       (INV_ERROR_BASE + 75)
+#define INV_ERROR_CALIBRATION_STORE      (INV_ERROR_BASE + 76)
+#define INV_ERROR_CALIBRATION_LEN        (INV_ERROR_BASE + 77)
+#define INV_ERROR_CALIBRATION_CHECKSUM   (INV_ERROR_BASE + 78)
+
+/* Accel errors */
+#define INV_ERROR_ACCEL_DATA_OVERFLOW    (INV_ERROR_BASE + 79)
+#define INV_ERROR_ACCEL_DATA_UNDERFLOW   (INV_ERROR_BASE + 80)
+#define INV_ERROR_ACCEL_DATA_NOT_READY   (INV_ERROR_BASE + 81)
+#define INV_ERROR_ACCEL_DATA_ERROR       (INV_ERROR_BASE + 82)
+
+#ifdef INV_USE_LEGACY_NAMES
+#define ML_SUCCESS                       INV_SUCCESS
+#define ML_ERROR                         INV_ERROR
+#define ML_ERROR_INVALID_PARAMETER       INV_ERROR_INVALID_PARAMETER
+#define ML_ERROR_FEATURE_NOT_ENABLED     INV_ERROR_FEATURE_NOT_ENABLED
+#define ML_ERROR_FEATURE_NOT_IMPLEMENTED INV_ERROR_FEATURE_NOT_IMPLEMENTED
+#define ML_ERROR_DMP_NOT_STARTED         INV_ERROR_DMP_NOT_STARTED
+#define ML_ERROR_DMP_STARTED             INV_ERROR_DMP_STARTED
+#define ML_ERROR_NOT_OPENED              INV_ERROR_NOT_OPENED
+#define ML_ERROR_OPENED                  INV_ERROR_OPENED
+#define ML_ERROR_INVALID_MODULE          INV_ERROR_INVALID_MODULE
+#define ML_ERROR_MEMORY_EXAUSTED         INV_ERROR_MEMORY_EXAUSTED
+#define ML_ERROR_DIVIDE_BY_ZERO          INV_ERROR_DIVIDE_BY_ZERO
+#define ML_ERROR_ASSERTION_FAILURE       INV_ERROR_ASSERTION_FAILURE
+#define ML_ERROR_FILE_OPEN               INV_ERROR_FILE_OPEN
+#define ML_ERROR_FILE_READ               INV_ERROR_FILE_READ
+#define ML_ERROR_FILE_WRITE              INV_ERROR_FILE_WRITE
+#define ML_ERROR_INVALID_CONFIGURATION   INV_ERROR_INVALID_CONFIGURATION
+#define ML_ERROR_SERIAL_CLOSED           INV_ERROR_SERIAL_CLOSED
+#define ML_ERROR_SERIAL_OPEN_ERROR       INV_ERROR_SERIAL_OPEN_ERROR
+#define ML_ERROR_SERIAL_READ             INV_ERROR_SERIAL_READ
+#define ML_ERROR_SERIAL_WRITE            INV_ERROR_SERIAL_WRITE
+#define ML_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED  \
+	INV_ERROR_SERIAL_DEVICE_NOT_RECOGNIZED
+#define ML_ERROR_SM_TRANSITION          INV_ERROR_SM_TRANSITION
+#define ML_ERROR_SM_IMPROPER_STATE      INV_ERROR_SM_IMPROPER_STATE
+#define ML_ERROR_FIFO_OVERFLOW          INV_ERROR_FIFO_OVERFLOW
+#define ML_ERROR_FIFO_FOOTER            INV_ERROR_FIFO_FOOTER
+#define ML_ERROR_FIFO_READ_COUNT        INV_ERROR_FIFO_READ_COUNT
+#define ML_ERROR_FIFO_READ_DATA         INV_ERROR_FIFO_READ_DATA
+#define ML_ERROR_MEMORY_SET             INV_ERROR_MEMORY_SET
+#define ML_ERROR_LOG_MEMORY_ERROR       INV_ERROR_LOG_MEMORY_ERROR
+#define ML_ERROR_LOG_OUTPUT_ERROR       INV_ERROR_LOG_OUTPUT_ERROR
+#define ML_ERROR_OS_BAD_PTR             INV_ERROR_OS_BAD_PTR
+#define ML_ERROR_OS_BAD_HANDLE          INV_ERROR_OS_BAD_HANDLE
+#define ML_ERROR_OS_CREATE_FAILED       INV_ERROR_OS_CREATE_FAILED
+#define ML_ERROR_OS_LOCK_FAILED         INV_ERROR_OS_LOCK_FAILED
+#define ML_ERROR_COMPASS_DATA_OVERFLOW  INV_ERROR_COMPASS_DATA_OVERFLOW
+#define ML_ERROR_COMPASS_DATA_UNDERFLOW INV_ERROR_COMPASS_DATA_UNDERFLOW
+#define ML_ERROR_COMPASS_DATA_NOT_READY INV_ERROR_COMPASS_DATA_NOT_READY
+#define ML_ERROR_COMPASS_DATA_ERROR     INV_ERROR_COMPASS_DATA_ERROR
+#define ML_ERROR_CALIBRATION_LOAD       INV_ERROR_CALIBRATION_LOAD
+#define ML_ERROR_CALIBRATION_STORE      INV_ERROR_CALIBRATION_STORE
+#define ML_ERROR_CALIBRATION_LEN        INV_ERROR_CALIBRATION_LEN
+#define ML_ERROR_CALIBRATION_CHECKSUM   INV_ERROR_CALIBRATION_CHECKSUM
+#define ML_ERROR_ACCEL_DATA_OVERFLOW    INV_ERROR_ACCEL_DATA_OVERFLOW
+#define ML_ERROR_ACCEL_DATA_UNDERFLOW   INV_ERROR_ACCEL_DATA_UNDERFLOW
+#define ML_ERROR_ACCEL_DATA_NOT_READY   INV_ERROR_ACCEL_DATA_NOT_READY
+#define ML_ERROR_ACCEL_DATA_ERROR       INV_ERROR_ACCEL_DATA_ERROR
+#endif
+
+/* For Linux coding compliance */
+
+#endif				/* MLTYPES_H */
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 07/11] misc: I2C communication with the MPU3050 and slave devices
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (4 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 06/11] misc: inv_mpu logging and debugging support Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 08/11] misc: Kconfig and Makefile changes for inv_mpu driver Nathan Royer
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/mlsl.c |  389 +++++++++++++++++++++++++++++++++++++++++++
 drivers/misc/inv_mpu/mlsl.h |  186 +++++++++++++++++++++
 2 files changed, 575 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/mlsl.c
 create mode 100644 drivers/misc/inv_mpu/mlsl.h

diff --git a/drivers/misc/inv_mpu/mlsl.c b/drivers/misc/inv_mpu/mlsl.c
new file mode 100644
index 0000000..0d15559
--- /dev/null
+++ b/drivers/misc/inv_mpu/mlsl.c
@@ -0,0 +1,389 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+#include "mlsl.h"
+#include <linux/i2c.h>
+#  include "mpu3050.h"
+
+static int inv_i2c_write(struct i2c_adapter *i2c_adap,
+			    unsigned char address,
+			    unsigned int len, unsigned char const *data)
+{
+	struct i2c_msg msgs[1];
+	int res;
+
+	if (!data || !i2c_adap)
+		return -EINVAL;
+
+	msgs[0].addr = address;
+	msgs[0].flags = 0;	/* write */
+	msgs[0].buf = (unsigned char *)data;
+	msgs[0].len = len;
+
+	res = i2c_transfer(i2c_adap, msgs, 1);
+	if (res < 1) {
+		if (res == 0)
+			res = -EIO;
+		return res;
+	} else
+		return 0;
+}
+
+static int inv_i2c_write_register(struct i2c_adapter *i2c_adap,
+				     unsigned char address,
+				     unsigned char reg, unsigned char value)
+{
+	unsigned char data[2];
+
+	data[0] = reg;
+	data[1] = value;
+	return inv_i2c_write(i2c_adap, address, 2, data);
+}
+
+static int inv_i2c_read(struct i2c_adapter *i2c_adap,
+			   unsigned char address, unsigned char reg,
+			   unsigned int len, unsigned char *data)
+{
+	struct i2c_msg msgs[2];
+	int res;
+
+	if (!data || !i2c_adap)
+		return -EINVAL;
+
+	msgs[0].addr = address;
+	msgs[0].flags = 0;	/* write */
+	msgs[0].buf = &reg;
+	msgs[0].len = 1;
+
+	msgs[1].addr = address;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].buf = data;
+	msgs[1].len = len;
+
+	res = i2c_transfer(i2c_adap, msgs, 2);
+	if (res < 2) {
+		if (res >= 0)
+			res = -EIO;
+		return res;
+	} else
+		return 0;
+}
+
+static int mpu_memory_read(struct i2c_adapter *i2c_adap,
+			   unsigned char mpu_addr,
+			   unsigned short mem_addr,
+			   unsigned int len, unsigned char *data)
+{
+	unsigned char bank[2];
+	unsigned char addr[2];
+	unsigned char buf;
+
+	struct i2c_msg msgs[4];
+	int res;
+
+	if (!data || !i2c_adap)
+		return -EINVAL;
+
+	bank[0] = MPUREG_BANK_SEL;
+	bank[1] = mem_addr >> 8;
+
+	addr[0] = MPUREG_MEM_START_ADDR;
+	addr[1] = mem_addr & 0xFF;
+
+	buf = MPUREG_MEM_R_W;
+
+	/* write message */
+	msgs[0].addr = mpu_addr;
+	msgs[0].flags = 0;
+	msgs[0].buf = bank;
+	msgs[0].len = sizeof(bank);
+
+	msgs[1].addr = mpu_addr;
+	msgs[1].flags = 0;
+	msgs[1].buf = addr;
+	msgs[1].len = sizeof(addr);
+
+	msgs[2].addr = mpu_addr;
+	msgs[2].flags = 0;
+	msgs[2].buf = &buf;
+	msgs[2].len = 1;
+
+	msgs[3].addr = mpu_addr;
+	msgs[3].flags = I2C_M_RD;
+	msgs[3].buf = data;
+	msgs[3].len = len;
+
+	res = i2c_transfer(i2c_adap, msgs, 4);
+	if (res != 4) {
+		if (res >= 0)
+			res = -EIO;
+		return res;
+	} else
+		return 0;
+}
+
+static int mpu_memory_write(struct i2c_adapter *i2c_adap,
+			    unsigned char mpu_addr,
+			    unsigned short mem_addr,
+			    unsigned int len, unsigned char const *data)
+{
+	unsigned char bank[2];
+	unsigned char addr[2];
+	unsigned char buf[513];
+
+	struct i2c_msg msgs[3];
+	int res;
+
+	if (!data || !i2c_adap)
+		return -EINVAL;
+	if (len >= (sizeof(buf) - 1))
+		return -ENOMEM;
+
+	bank[0] = MPUREG_BANK_SEL;
+	bank[1] = mem_addr >> 8;
+
+	addr[0] = MPUREG_MEM_START_ADDR;
+	addr[1] = mem_addr & 0xFF;
+
+	buf[0] = MPUREG_MEM_R_W;
+	memcpy(buf + 1, data, len);
+
+	/* write message */
+	msgs[0].addr = mpu_addr;
+	msgs[0].flags = 0;
+	msgs[0].buf = bank;
+	msgs[0].len = sizeof(bank);
+
+	msgs[1].addr = mpu_addr;
+	msgs[1].flags = 0;
+	msgs[1].buf = addr;
+	msgs[1].len = sizeof(addr);
+
+	msgs[2].addr = mpu_addr;
+	msgs[2].flags = 0;
+	msgs[2].buf = (unsigned char *)buf;
+	msgs[2].len = len + 1;
+
+	res = i2c_transfer(i2c_adap, msgs, 3);
+	if (res != 3) {
+		if (res >= 0)
+			res = -EIO;
+		return res;
+	} else
+		return 0;
+}
+
+int inv_serial_single_write(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned char register_addr,
+	unsigned char data)
+{
+	return inv_i2c_write_register((struct i2c_adapter *)sl_handle,
+				      slave_addr, register_addr, data);
+}
+EXPORT_SYMBOL(inv_serial_single_write);
+
+int inv_serial_write(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short length,
+	unsigned char const *data)
+{
+	int result;
+	const unsigned short data_length = length - 1;
+	const unsigned char start_reg_addr = data[0];
+	unsigned char i2c_write[SERIAL_MAX_TRANSFER_SIZE + 1];
+	unsigned short bytes_written = 0;
+
+	while (bytes_written < data_length) {
+		unsigned short this_len = min(SERIAL_MAX_TRANSFER_SIZE,
+					     data_length - bytes_written);
+		if (bytes_written == 0) {
+			result = inv_i2c_write((struct i2c_adapter *)
+					       sl_handle, slave_addr,
+					       1 + this_len, data);
+		} else {
+			/* manually increment register addr between chunks */
+			i2c_write[0] = start_reg_addr + bytes_written;
+			memcpy(&i2c_write[1], &data[1 + bytes_written],
+				this_len);
+			result = inv_i2c_write((struct i2c_adapter *)
+					       sl_handle, slave_addr,
+					       1 + this_len, i2c_write);
+		}
+		if (result)
+			return result;
+		bytes_written += this_len;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(inv_serial_write);
+
+int inv_serial_read(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned char register_addr,
+	unsigned short length,
+	unsigned char *data)
+{
+	int result;
+	unsigned short bytes_read = 0;
+
+	if (register_addr == MPUREG_FIFO_R_W || register_addr == MPUREG_MEM_R_W)
+		return INV_ERROR_INVALID_PARAMETER;
+
+	while (bytes_read < length) {
+		unsigned short this_len =
+		    min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read);
+		result = inv_i2c_read((struct i2c_adapter *)sl_handle,
+				      slave_addr, register_addr + bytes_read,
+				      this_len, &data[bytes_read]);
+		if (result)
+			return result;
+		bytes_read += this_len;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(inv_serial_read);
+
+int inv_serial_write_mem(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short mem_addr,
+	unsigned short length,
+	unsigned char const *data)
+{
+	int result;
+	unsigned short bytes_written = 0;
+
+	if ((mem_addr & 0xFF) + length > MPU_MEM_BANK_SIZE) {
+		pr_err("memory read length (%d B) extends beyond its"
+		       " limits (%d) if started at location %d\n", length,
+		       MPU_MEM_BANK_SIZE, mem_addr & 0xFF);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+	while (bytes_written < length) {
+		unsigned short this_len =
+		    min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_written);
+		result = mpu_memory_write((struct i2c_adapter *)sl_handle,
+					  slave_addr, mem_addr + bytes_written,
+					  this_len, &data[bytes_written]);
+		if (result)
+			return result;
+		bytes_written += this_len;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(inv_serial_write_mem);
+
+int inv_serial_read_mem(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short mem_addr,
+	unsigned short length,
+	unsigned char *data)
+{
+	int result;
+	unsigned short bytes_read = 0;
+
+	if ((mem_addr & 0xFF) + length > MPU_MEM_BANK_SIZE) {
+		printk
+		    ("memory read length (%d B) extends beyond its limits (%d) "
+		     "if started at location %d\n", length,
+		     MPU_MEM_BANK_SIZE, mem_addr & 0xFF);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+	while (bytes_read < length) {
+		unsigned short this_len =
+		    min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read);
+		result =
+		    mpu_memory_read((struct i2c_adapter *)sl_handle,
+				    slave_addr, mem_addr + bytes_read,
+				    this_len, &data[bytes_read]);
+		if (result)
+			return result;
+		bytes_read += this_len;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(inv_serial_read_mem);
+
+int inv_serial_write_fifo(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short length,
+	unsigned char const *data)
+{
+	int result;
+	unsigned char i2c_write[SERIAL_MAX_TRANSFER_SIZE + 1];
+	unsigned short bytes_written = 0;
+
+	if (length > FIFO_HW_SIZE) {
+		printk(KERN_ERR
+		       "maximum fifo write length is %d\n", FIFO_HW_SIZE);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+	while (bytes_written < length) {
+		unsigned short this_len =
+		    min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_written);
+		i2c_write[0] = MPUREG_FIFO_R_W;
+		memcpy(&i2c_write[1], &data[bytes_written], this_len);
+		result = inv_i2c_write((struct i2c_adapter *)sl_handle,
+				       slave_addr, this_len + 1, i2c_write);
+		if (result)
+			return result;
+		bytes_written += this_len;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(inv_serial_write_fifo);
+
+int inv_serial_read_fifo(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short length,
+	unsigned char *data)
+{
+	int result;
+	unsigned short bytes_read = 0;
+
+	if (length > FIFO_HW_SIZE) {
+		printk(KERN_ERR
+		       "maximum fifo read length is %d\n", FIFO_HW_SIZE);
+		return INV_ERROR_INVALID_PARAMETER;
+	}
+	while (bytes_read < length) {
+		unsigned short this_len =
+		    min(SERIAL_MAX_TRANSFER_SIZE, length - bytes_read);
+		result = inv_i2c_read((struct i2c_adapter *)sl_handle,
+				      slave_addr, MPUREG_FIFO_R_W, this_len,
+				      &data[bytes_read]);
+		if (result)
+			return result;
+		bytes_read += this_len;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(inv_serial_read_fifo);
+
+/**
+ *  @}
+ */
diff --git a/drivers/misc/inv_mpu/mlsl.h b/drivers/misc/inv_mpu/mlsl.h
new file mode 100644
index 0000000..204baed
--- /dev/null
+++ b/drivers/misc/inv_mpu/mlsl.h
@@ -0,0 +1,186 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+#ifndef __MLSL_H__
+#define __MLSL_H__
+
+/**
+ *  @defgroup   MLSL
+ *  @brief      Motion Library - Serial Layer.
+ *              The Motion Library System Layer provides the Motion Library
+ *              with the communication interface to the hardware.
+ *
+ *              The communication interface is assumed to support serial
+ *              transfers in burst of variable length up to
+ *              SERIAL_MAX_TRANSFER_SIZE.
+ *              The default value for SERIAL_MAX_TRANSFER_SIZE is 128 bytes.
+ *              Transfers of length greater than SERIAL_MAX_TRANSFER_SIZE, will
+ *              be subdivided in smaller transfers of length <=
+ *              SERIAL_MAX_TRANSFER_SIZE.
+ *              The SERIAL_MAX_TRANSFER_SIZE definition can be modified to
+ *              overcome any host processor transfer size limitation down to
+ *              1 B, the minimum.
+ *              An higher value for SERIAL_MAX_TRANSFER_SIZE will favor
+ *              performance and efficiency while requiring higher resource usage
+ *              (mostly buffering). A smaller value will increase overhead and
+ *              decrease efficiency but allows to operate with more resource
+ *              constrained processor and master serial controllers.
+ *              The SERIAL_MAX_TRANSFER_SIZE definition can be found in the
+ *              mlsl.h header file and master serial controllers.
+ *              The SERIAL_MAX_TRANSFER_SIZE definition can be found in the
+ *              mlsl.h header file.
+ *
+ *  @{
+ *      @file   mlsl.h
+ *      @brief  The Motion Library System Layer.
+ *
+ */
+
+#include "mltypes.h"
+#include <linux/mpu.h>
+
+
+/*
+ * NOTE : to properly support Yamaha compass reads,
+ *	  the max transfer size should be at least 9 B.
+ *	  Length in bytes, typically a power of 2 >= 2
+ */
+#define SERIAL_MAX_TRANSFER_SIZE 128
+
+
+/**
+ *  inv_serial_single_write() - used to write a single byte of data.
+ *  @sl_handle		pointer to the serial device used for the communication.
+ *  @slave_addr		I2C slave address of device.
+ *  @register_addr	Register address to write.
+ *  @data		Single byte of data to write.
+ *
+ *	It is called by the MPL to write a single byte of data to the MPU.
+ *
+ *  returns INV_SUCCESS if successful, a non-zero error code otherwise.
+ */
+int inv_serial_single_write(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned char register_addr,
+	unsigned char data);
+
+/**
+ *  inv_serial_write() - used to write multiple bytes of data to registers.
+ *  @sl_handle	a file handle to the serial device used for the communication.
+ *  @slave_addr	I2C slave address of device.
+ *  @register_addr	Register address to write.
+ *  @length	Length of burst of data.
+ *  @data	Pointer to block of data.
+ *
+ *  returns INV_SUCCESS if successful, a non-zero error code otherwise.
+ */
+int inv_serial_write(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short length,
+	unsigned char const *data);
+
+/**
+ *  inv_serial_read() - used to read multiple bytes of data from registers.
+ *  @sl_handle	a file handle to the serial device used for the communication.
+ *  @slave_addr	I2C slave address of device.
+ *  @register_addr	Register address to read.
+ *  @length	Length of burst of data.
+ *  @data	Pointer to block of data.
+ *
+ *  returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise.
+ */
+int inv_serial_read(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned char register_addr,
+	unsigned short length,
+	unsigned char *data);
+
+/**
+ *  inv_serial_read_mem() - used to read multiple bytes of data from the memory.
+ *	    This should be sent by I2C or SPI.
+ *
+ *  @sl_handle	a file handle to the serial device used for the communication.
+ *  @slave_addr	I2C slave address of device.
+ *  @mem_addr	The location in the memory to read from.
+ *  @length	Length of burst data.
+ *  @data	Pointer to block of data.
+ *
+ *  returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise.
+ */
+int inv_serial_read_mem(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short mem_addr,
+	unsigned short length,
+	unsigned char *data);
+
+/**
+ *  inv_serial_write_mem() - used to write multiple bytes of data to the memory.
+ *  @sl_handle	a file handle to the serial device used for the communication.
+ *  @slave_addr	I2C slave address of device.
+ *  @mem_addr	The location in the memory to write to.
+ *  @length	Length of burst data.
+ *  @data	Pointer to block of data.
+ *
+ *  returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise.
+ */
+int inv_serial_write_mem(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short mem_addr,
+	unsigned short length,
+	unsigned char const *data);
+
+/**
+ *  inv_serial_read_fifo() - used to read multiple bytes of data from the fifo.
+ *  @sl_handle	a file handle to the serial device used for the communication.
+ *  @slave_addr	I2C slave address of device.
+ *  @length	Length of burst of data.
+ *  @data	Pointer to block of data.
+ *
+ *  returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise.
+ */
+int inv_serial_read_fifo(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short length,
+	unsigned char *data);
+
+/**
+ *  inv_serial_write_fifo() - used to write multiple bytes of data to the fifo.
+ *  @sl_handle	a file handle to the serial device used for the communication.
+ *  @slave_addr	I2C slave address of device.
+ *  @length	Length of burst of data.
+ *  @data	Pointer to block of data.
+ *
+ *  returns INV_SUCCESS == 0 if successful; a non-zero error code otherwise.
+ */
+int inv_serial_write_fifo(
+	void *sl_handle,
+	unsigned char slave_addr,
+	unsigned short length,
+	unsigned char const *data);
+
+/**
+ * @}
+ */
+#endif				/* __MLSL_H__ */
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 08/11] misc: Kconfig and Makefile changes for inv_mpu driver
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (5 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 07/11] misc: I2C communication with the MPU3050 and slave devices Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01 17:10   ` Randy Dunlap
  2011-07-01  2:18 ` [PATCH 09/11] misc: Add slave driver for kxtf9 accelerometer Nathan Royer
                   ` (7 subsequent siblings)
  14 siblings, 1 reply; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/Kconfig          |    1 +
 drivers/misc/Makefile         |    1 +
 drivers/misc/inv_mpu/Kconfig  |   58 +++++++++++++++++++++++++++++++++++++++++
 drivers/misc/inv_mpu/Makefile |   17 ++++++++++++
 4 files changed, 77 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/Kconfig
 create mode 100644 drivers/misc/inv_mpu/Makefile

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 4e349cd..2dc231d 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -493,6 +493,7 @@ config PCH_PHUB
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
+source "drivers/misc/inv_mpu/Kconfig"
 source "drivers/misc/iwmc3200top/Kconfig"
 source "drivers/misc/ti-st/Kconfig"
 source "drivers/misc/lis3lv02d/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 5f03172..3b371cf 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_DS1682)		+= ds1682.o
 obj-$(CONFIG_TI_DAC7512)	+= ti_dac7512.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-$(CONFIG_IWMC3200TOP)      += iwmc3200top/
+obj-y				+= inv_mpu/
 obj-$(CONFIG_HMC6352)		+= hmc6352.o
 obj-y				+= eeprom/
 obj-y				+= cb710/
diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
new file mode 100644
index 0000000..fdfa7a6
--- /dev/null
+++ b/drivers/misc/inv_mpu/Kconfig
@@ -0,0 +1,58 @@
+
+menuconfig: INV_SENSORS
+	tristate "Motion Processing Unit"
+	depends on I2C
+	default y
+
+if INV_SENSORS
+
+choice
+	prompt "MPU Master"
+	depends on I2C && INV_SENSORS
+	default MPU_SENSORS_MPU3050
+
+config MPU_SENSORS_MPU3050
+	bool "MPU3050"
+	depends on I2C
+	select MPU_SENSORS_MPU3050_GYRO
+	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 MPU_SENSORS_MPU6050A2
+	bool "MPU6050A2"
+	depends on I2C
+	select MPU_SENSORS_MPU6050_GYRO
+	help
+	  If you say yes here you get support for the MPU6050A2 Gyroscope driver
+	  This driver can also be built as a module.  If so, the module
+	  will be called mpu6050a2.
+
+config MPU_SENSORS_MPU6050B1
+	bool "MPU6050B1"
+	select MPU_SENSORS_MPU6050_GYRO
+	depends on I2C
+	help
+	  If you say yes here you get support for the MPU6050 Gyroscope driver
+	  This driver can also be built as a module.  If so, the module
+	  will be called mpu6050b1.
+
+endchoice
+
+choice
+	prompt "Gyroscope Type"
+	depends on I2C && INV_SENSORS
+	default MPU_SENSORS_MPU3050_GYRO
+
+config MPU_SENSORS_MPU3050_GYRO
+	bool "MPU3050 built in gyroscope"
+	depends on MPU_SENSORS_MPU3050
+
+config MPU_SENSORS_MPU6050_GYRO
+	bool "MPU6050 built in gyroscope"
+	depends on MPU_SENSORS_MPU6050B1 || MPU_SENSORS_MPU6050A2
+
+endchoice
+
+endif #INV_SENSORS
diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile
new file mode 100644
index 0000000..48fc55a
--- /dev/null
+++ b/drivers/misc/inv_mpu/Makefile
@@ -0,0 +1,17 @@
+
+# Kernel makefile for motions sensors
+#
+#
+
+# MPU
+
+obj-$(CONFIG_INV_SENSORS)	+= mpu3050.o
+
+mpu3050-objs += mpuirq.o
+mpu3050-objs += slaveirq.o
+mpu3050-objs += mpu-dev.o
+mpu3050-objs += mlsl.o
+mpu3050-objs += mldl_cfg.o
+mpu3050-objs += mldl_print_cfg.o
+
+EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 09/11] misc: Add slave driver for kxtf9 accelerometer
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (6 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 08/11] misc: Kconfig and Makefile changes for inv_mpu driver Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 10/11] misc: Add slave driver for ak8975 compass driver Nathan Royer
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/Kconfig        |    2 +
 drivers/misc/inv_mpu/Makefile       |    2 +
 drivers/misc/inv_mpu/accel/Kconfig  |   23 +
 drivers/misc/inv_mpu/accel/Makefile |    7 +
 drivers/misc/inv_mpu/accel/kxtf9.c  |  841 +++++++++++++++++++++++++++++++++++
 5 files changed, 875 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/accel/Kconfig
 create mode 100644 drivers/misc/inv_mpu/accel/Makefile
 create mode 100644 drivers/misc/inv_mpu/accel/kxtf9.c

diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
index fdfa7a6..6116010 100644
--- a/drivers/misc/inv_mpu/Kconfig
+++ b/drivers/misc/inv_mpu/Kconfig
@@ -55,4 +55,6 @@ config MPU_SENSORS_MPU6050_GYRO
 
 endchoice
 
+source "drivers/misc/inv_mpu/accel/Kconfig"
+
 endif #INV_SENSORS
diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile
index 48fc55a..d2c2c9a 100644
--- a/drivers/misc/inv_mpu/Makefile
+++ b/drivers/misc/inv_mpu/Makefile
@@ -15,3 +15,5 @@ mpu3050-objs += mldl_cfg.o
 mpu3050-objs += mldl_print_cfg.o
 
 EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
+
+obj-y			+= accel/
diff --git a/drivers/misc/inv_mpu/accel/Kconfig b/drivers/misc/inv_mpu/accel/Kconfig
new file mode 100644
index 0000000..5e7aeaf
--- /dev/null
+++ b/drivers/misc/inv_mpu/accel/Kconfig
@@ -0,0 +1,23 @@
+menuconfig INV_SENSORS_ACCELEROMETERS
+	bool "Accelerometer Slave Sensors"
+	default y
+	---help---
+	  Say Y here to get to see options for device drivers for various
+	  accelerometrs for integration with the MPU3050 or MPU6050 driver.
+	  This option alone does not add any kernel code.
+
+	  If you say N, all options in this submenu will be skipped and disabled.
+
+if INV_SENSORS_ACCELEROMETERS
+
+config MPU_SENSORS_KXTF9
+	tristate "Kionix KXTF9"
+	depends on MPU_SENSORS_MPU3050 || MPU_SENSORS_MPU6050B1 || MPU_SENSORS_MPU6050A2
+	help
+	  This enables support for the Kionix KXFT9 accelerometer
+	  This support is for integration with the MPU3050 gyroscope device
+	  driver.  Only one accelerometer can be registered at a time.
+	  Specifying more that one accelerometer in the board file will result
+	  in runtime errors.
+
+endif
diff --git a/drivers/misc/inv_mpu/accel/Makefile b/drivers/misc/inv_mpu/accel/Makefile
new file mode 100644
index 0000000..9592973
--- /dev/null
+++ b/drivers/misc/inv_mpu/accel/Makefile
@@ -0,0 +1,7 @@
+#
+# Accel Slaves to MPUxxxx
+#
+obj-$(CONFIG_MPU_SENSORS_KXTF9) += inv_mpu_kxtf9.o
+inv_mpu_kxtf9-objs += kxtf9.o
+
+EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
diff --git a/drivers/misc/inv_mpu/accel/kxtf9.c b/drivers/misc/inv_mpu/accel/kxtf9.c
new file mode 100644
index 0000000..33fafc8
--- /dev/null
+++ b/drivers/misc/inv_mpu/accel/kxtf9.c
@@ -0,0 +1,841 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ *  @addtogroup ACCELDL
+ *  @brief      Accelerometer setup and handling methods for Kionix KXTF9.
+ *
+ *  @{
+ *      @file   kxtf9.c
+ *      @brief  Accelerometer setup and handling methods for Kionix KXTF9.
+*/
+
+/* -------------------------------------------------------------------------- */
+
+#undef MPL_LOG_NDEBUG
+#define MPL_LOG_NDEBUG 1
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include "mpu-dev.h"
+
+#include <log.h>
+#include <linux/mpu.h>
+#include "mlsl.h"
+#include "mldl_cfg.h"
+#undef MPL_LOG_TAG
+#define MPL_LOG_TAG "MPL-acc"
+
+#define KXTF9_XOUT_HPF_L                (0x00)	/* 0000 0000 */
+#define KXTF9_XOUT_HPF_H                (0x01)	/* 0000 0001 */
+#define KXTF9_YOUT_HPF_L                (0x02)	/* 0000 0010 */
+#define KXTF9_YOUT_HPF_H                (0x03)	/* 0000 0011 */
+#define KXTF9_ZOUT_HPF_L                (0x04)	/* 0001 0100 */
+#define KXTF9_ZOUT_HPF_H                (0x05)	/* 0001 0101 */
+#define KXTF9_XOUT_L                    (0x06)	/* 0000 0110 */
+#define KXTF9_XOUT_H                    (0x07)	/* 0000 0111 */
+#define KXTF9_YOUT_L                    (0x08)	/* 0000 1000 */
+#define KXTF9_YOUT_H                    (0x09)	/* 0000 1001 */
+#define KXTF9_ZOUT_L                    (0x0A)	/* 0001 1010 */
+#define KXTF9_ZOUT_H                    (0x0B)	/* 0001 1011 */
+#define KXTF9_ST_RESP                   (0x0C)	/* 0000 1100 */
+#define KXTF9_WHO_AM_I                  (0x0F)	/* 0000 1111 */
+#define KXTF9_TILT_POS_CUR              (0x10)	/* 0001 0000 */
+#define KXTF9_TILT_POS_PRE              (0x11)	/* 0001 0001 */
+#define KXTF9_INT_SRC_REG1              (0x15)	/* 0001 0101 */
+#define KXTF9_INT_SRC_REG2              (0x16)	/* 0001 0110 */
+#define KXTF9_STATUS_REG                (0x18)	/* 0001 1000 */
+#define KXTF9_INT_REL                   (0x1A)	/* 0001 1010 */
+#define KXTF9_CTRL_REG1                 (0x1B)	/* 0001 1011 */
+#define KXTF9_CTRL_REG2                 (0x1C)	/* 0001 1100 */
+#define KXTF9_CTRL_REG3                 (0x1D)	/* 0001 1101 */
+#define KXTF9_INT_CTRL_REG1             (0x1E)	/* 0001 1110 */
+#define KXTF9_INT_CTRL_REG2             (0x1F)	/* 0001 1111 */
+#define KXTF9_INT_CTRL_REG3             (0x20)	/* 0010 0000 */
+#define KXTF9_DATA_CTRL_REG             (0x21)	/* 0010 0001 */
+#define KXTF9_TILT_TIMER                (0x28)	/* 0010 1000 */
+#define KXTF9_WUF_TIMER                 (0x29)	/* 0010 1001 */
+#define KXTF9_TDT_TIMER                 (0x2B)	/* 0010 1011 */
+#define KXTF9_TDT_H_THRESH              (0x2C)	/* 0010 1100 */
+#define KXTF9_TDT_L_THRESH              (0x2D)	/* 0010 1101 */
+#define KXTF9_TDT_TAP_TIMER             (0x2E)	/* 0010 1110 */
+#define KXTF9_TDT_TOTAL_TIMER           (0x2F)	/* 0010 1111 */
+#define KXTF9_TDT_LATENCY_TIMER         (0x30)	/* 0011 0000 */
+#define KXTF9_TDT_WINDOW_TIMER          (0x31)	/* 0011 0001 */
+#define KXTF9_WUF_THRESH                (0x5A)	/* 0101 1010 */
+#define KXTF9_TILT_ANGLE                (0x5C)	/* 0101 1100 */
+#define KXTF9_HYST_SET                  (0x5F)	/* 0101 1111 */
+
+#define KXTF9_MAX_DUR (0xFF)
+#define KXTF9_MAX_THS (0xFF)
+#define KXTF9_THS_COUNTS_P_G (32)
+
+/* -------------------------------------------------------------------------- */
+
+struct kxtf9_config {
+	unsigned int odr;	/* Output data rate mHz */
+	unsigned int fsr;	/* full scale range mg */
+	unsigned int ths;	/* Motion no-motion thseshold mg */
+	unsigned int dur;	/* Motion no-motion duration ms */
+	unsigned int irq_type;
+	unsigned char reg_ths;
+	unsigned char reg_dur;
+	unsigned char reg_odr;
+	unsigned char reg_int_cfg1;
+	unsigned char reg_int_cfg2;
+	unsigned char ctrl_reg1;
+};
+
+struct kxtf9_private_data {
+	struct kxtf9_config suspend;
+	struct kxtf9_config resume;
+};
+
+static int kxtf9_set_ths(void *mlsl_handle,
+			 struct ext_slave_platform_data *pdata,
+			 struct kxtf9_config *config, int apply, long ths)
+{
+	int result = INV_SUCCESS;
+	if ((ths * KXTF9_THS_COUNTS_P_G / 1000) > KXTF9_MAX_THS)
+		ths = (KXTF9_MAX_THS * 1000) / KXTF9_THS_COUNTS_P_G;
+
+	if (ths < 0)
+		ths = 0;
+
+	config->ths = ths;
+	config->reg_ths = (unsigned char)
+	    ((long)(ths * KXTF9_THS_COUNTS_P_G) / 1000);
+	MPL_LOGV("THS: %d, 0x%02x\n", config->ths, (int)config->reg_ths);
+	if (apply)
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_WUF_THRESH,
+						 config->reg_ths);
+	return result;
+}
+
+static int kxtf9_set_dur(void *mlsl_handle,
+			 struct ext_slave_platform_data *pdata,
+			 struct kxtf9_config *config, int apply, long dur)
+{
+	int result = INV_SUCCESS;
+	long reg_dur = (dur * config->odr) / 1000000;
+	config->dur = dur;
+
+	if (reg_dur > KXTF9_MAX_DUR)
+		reg_dur = KXTF9_MAX_DUR;
+
+	config->reg_dur = (unsigned char)reg_dur;
+	MPL_LOGV("DUR: %d, 0x%02x\n", config->dur, (int)config->reg_dur);
+	if (apply)
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_WUF_TIMER,
+						 (unsigned char)reg_dur);
+	return result;
+}
+
+/**
+ * Sets the IRQ to fire when one of the IRQ events occur.  Threshold and
+ * duration will not be used uless the type is MOT or NMOT.
+ *
+ * @param config configuration to apply to, suspend or resume
+ * @param irq_type The type of IRQ.  Valid values are
+ * - MPU_SLAVE_IRQ_TYPE_NONE
+ * - MPU_SLAVE_IRQ_TYPE_MOTION
+ * - MPU_SLAVE_IRQ_TYPE_DATA_READY
+ */
+static int kxtf9_set_irq(void *mlsl_handle,
+			 struct ext_slave_platform_data *pdata,
+			 struct kxtf9_config *config, int apply, long irq_type)
+{
+	int result = INV_SUCCESS;
+	struct kxtf9_private_data *private_data = pdata->private_data;
+
+	config->irq_type = (unsigned char)irq_type;
+	config->ctrl_reg1 &= ~0x22;
+	if (irq_type == MPU_SLAVE_IRQ_TYPE_DATA_READY) {
+		config->ctrl_reg1 |= 0x20;
+		config->reg_int_cfg1 = 0x38;
+		config->reg_int_cfg2 = 0x00;
+	} else if (irq_type == MPU_SLAVE_IRQ_TYPE_MOTION) {
+		config->ctrl_reg1 |= 0x02;
+		if ((unsigned long)config ==
+		    (unsigned long)&private_data->suspend)
+			config->reg_int_cfg1 = 0x34;
+		else
+			config->reg_int_cfg1 = 0x24;
+		config->reg_int_cfg2 = 0xE0;
+	} else {
+		config->reg_int_cfg1 = 0x00;
+		config->reg_int_cfg2 = 0x00;
+	}
+
+	if (apply) {
+		/* Must clear bit 7 before writing new configuration */
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_CTRL_REG1, 0x40);
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_INT_CTRL_REG1,
+						 config->reg_int_cfg1);
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_INT_CTRL_REG2,
+						 config->reg_int_cfg2);
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_CTRL_REG1,
+						 config->ctrl_reg1);
+	}
+	MPL_LOGV("CTRL_REG1: %lx, INT_CFG1: %lx, INT_CFG2: %lx\n",
+		 (unsigned long)config->ctrl_reg1,
+		 (unsigned long)config->reg_int_cfg1,
+		 (unsigned long)config->reg_int_cfg2);
+
+	return result;
+}
+
+/**
+ * Set the Output data rate for the particular configuration
+ *
+ * @param config Config to modify with new ODR
+ * @param odr Output data rate in units of 1/1000Hz
+ */
+static int kxtf9_set_odr(void *mlsl_handle,
+			 struct ext_slave_platform_data *pdata,
+			 struct kxtf9_config *config, int apply, long odr)
+{
+	unsigned char bits;
+	int result = INV_SUCCESS;
+
+	/* Data sheet says there is 12.5 hz, but that seems to produce a single
+	 * correct data value, thus we remove it from the table */
+	if (odr > 400000) {
+		config->odr = 800000;
+		bits = 0x06;
+	} else if (odr > 200000) {
+		config->odr = 400000;
+		bits = 0x05;
+	} else if (odr > 100000) {
+		config->odr = 200000;
+		bits = 0x04;
+	} else if (odr > 50000) {
+		config->odr = 100000;
+		bits = 0x03;
+	} else if (odr > 25000) {
+		config->odr = 50000;
+		bits = 0x02;
+	} else if (odr != 0) {
+		config->odr = 25000;
+		bits = 0x01;
+	} else {
+		config->odr = 0;
+		bits = 0;
+	}
+
+	if (odr != 0)
+		config->ctrl_reg1 |= 0x80;
+	else
+		config->ctrl_reg1 &= ~0x80;
+
+	config->reg_odr = bits;
+	kxtf9_set_dur(mlsl_handle, pdata, config, apply, config->dur);
+	MPL_LOGV("ODR: %d, 0x%02x\n", config->odr, (int)config->ctrl_reg1);
+	if (apply) {
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_DATA_CTRL_REG,
+						 config->reg_odr);
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_CTRL_REG1, 0x40);
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_CTRL_REG1,
+						 config->ctrl_reg1);
+	}
+	return result;
+}
+
+/**
+ * Set the full scale range of the accels
+ *
+ * @param config pointer to configuration
+ * @param fsr requested full scale range
+ */
+static int kxtf9_set_fsr(void *mlsl_handle,
+			 struct ext_slave_platform_data *pdata,
+			 struct kxtf9_config *config, int apply, long fsr)
+{
+	int result = INV_SUCCESS;
+
+	config->ctrl_reg1 = (config->ctrl_reg1 & 0xE7);
+	if (fsr <= 2000) {
+		config->fsr = 2000;
+		config->ctrl_reg1 |= 0x00;
+	} else if (fsr <= 4000) {
+		config->fsr = 4000;
+		config->ctrl_reg1 |= 0x08;
+	} else {
+		config->fsr = 8000;
+		config->ctrl_reg1 |= 0x10;
+	}
+
+	MPL_LOGV("FSR: %d\n", config->fsr);
+	if (apply) {
+		/* Must clear bit 7 before writing new configuration */
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_CTRL_REG1, 0x40);
+		result = inv_serial_single_write(mlsl_handle, pdata->address,
+						 KXTF9_CTRL_REG1,
+						 config->ctrl_reg1);
+	}
+	return result;
+}
+
+static int kxtf9_suspend(void *mlsl_handle,
+			 struct ext_slave_descr *slave,
+			 struct ext_slave_platform_data *pdata)
+{
+	int result;
+	unsigned char data;
+	struct kxtf9_private_data *private_data = pdata->private_data;
+
+	/* Wake up */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_CTRL_REG1, 0x40);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* INT_CTRL_REG1: */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_INT_CTRL_REG1,
+					 private_data->suspend.reg_int_cfg1);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* WUF_THRESH: */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_WUF_THRESH,
+					 private_data->suspend.reg_ths);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* DATA_CTRL_REG */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_DATA_CTRL_REG,
+					 private_data->suspend.reg_odr);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* WUF_TIMER */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_WUF_TIMER,
+					 private_data->suspend.reg_dur);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Normal operation  */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_CTRL_REG1,
+					 private_data->suspend.ctrl_reg1);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_read(mlsl_handle, pdata->address,
+				 KXTF9_INT_REL, 1, &data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	return result;
+}
+
+/* full scale setting - register and mask */
+#define ACCEL_KIONIX_CTRL_REG      (0x1b)
+#define ACCEL_KIONIX_CTRL_MASK     (0x18)
+
+static int kxtf9_resume(void *mlsl_handle,
+			struct ext_slave_descr *slave,
+			struct ext_slave_platform_data *pdata)
+{
+	int result = INV_SUCCESS;
+	unsigned char data;
+	struct kxtf9_private_data *private_data = pdata->private_data;
+
+	/* Wake up */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_CTRL_REG1, 0x40);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* INT_CTRL_REG1: */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_INT_CTRL_REG1,
+					 private_data->resume.reg_int_cfg1);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* WUF_THRESH: */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_WUF_THRESH,
+					 private_data->resume.reg_ths);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* DATA_CTRL_REG */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_DATA_CTRL_REG,
+					 private_data->resume.reg_odr);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* WUF_TIMER */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_WUF_TIMER,
+					 private_data->resume.reg_dur);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Normal operation  */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_CTRL_REG1,
+					 private_data->resume.ctrl_reg1);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = inv_serial_read(mlsl_handle, pdata->address,
+				 KXTF9_INT_REL, 1, &data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	return INV_SUCCESS;
+}
+
+static int kxtf9_init(void *mlsl_handle,
+		      struct ext_slave_descr *slave,
+		      struct ext_slave_platform_data *pdata)
+{
+
+	struct kxtf9_private_data *private_data;
+	int result = INV_SUCCESS;
+
+	private_data = (struct kxtf9_private_data *)
+	    kzalloc(sizeof(struct kxtf9_private_data), GFP_KERNEL);
+
+	if (!private_data)
+		return INV_ERROR_MEMORY_EXAUSTED;
+
+	/* RAM reset */
+	/* Fastest Reset */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_CTRL_REG1, 0x40);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* Fastest Reset */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_DATA_CTRL_REG, 0x36);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* Reset */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 KXTF9_CTRL_REG3, 0xcd);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	msleep(2);
+
+	pdata->private_data = private_data;
+
+	private_data->resume.ctrl_reg1 = 0xC0;
+	private_data->suspend.ctrl_reg1 = 0x40;
+
+	result = kxtf9_set_dur(mlsl_handle, pdata, &private_data->suspend,
+			       false, 1000);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = kxtf9_set_dur(mlsl_handle, pdata, &private_data->resume,
+			       false, 2540);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = kxtf9_set_odr(mlsl_handle, pdata, &private_data->suspend,
+			       false, 50000);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = kxtf9_set_odr(mlsl_handle, pdata, &private_data->resume,
+			       false, 200000);
+
+	result = kxtf9_set_fsr(mlsl_handle, pdata, &private_data->suspend,
+			       false, 2000);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = kxtf9_set_fsr(mlsl_handle, pdata, &private_data->resume,
+			       false, 2000);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = kxtf9_set_ths(mlsl_handle, pdata, &private_data->suspend,
+			       false, 80);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = kxtf9_set_ths(mlsl_handle, pdata, &private_data->resume,
+			       false, 40);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	result = kxtf9_set_irq(mlsl_handle, pdata, &private_data->suspend,
+			       false, MPU_SLAVE_IRQ_TYPE_NONE);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	result = kxtf9_set_irq(mlsl_handle, pdata, &private_data->resume,
+			       false, MPU_SLAVE_IRQ_TYPE_NONE);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	return result;
+}
+
+static int kxtf9_exit(void *mlsl_handle,
+		      struct ext_slave_descr *slave,
+		      struct ext_slave_platform_data *pdata)
+{
+	kfree(pdata->private_data);
+	return INV_SUCCESS;
+}
+
+static int kxtf9_config(void *mlsl_handle,
+			struct ext_slave_descr *slave,
+			struct ext_slave_platform_data *pdata,
+			struct ext_slave_config *data)
+{
+	struct kxtf9_private_data *private_data = pdata->private_data;
+	if (!data->data)
+		return INV_ERROR_INVALID_PARAMETER;
+
+	switch (data->key) {
+	case MPU_SLAVE_CONFIG_ODR_SUSPEND:
+		return kxtf9_set_odr(mlsl_handle, pdata,
+				     &private_data->suspend,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_ODR_RESUME:
+		return kxtf9_set_odr(mlsl_handle, pdata,
+				     &private_data->resume,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_FSR_SUSPEND:
+		return kxtf9_set_fsr(mlsl_handle, pdata,
+				     &private_data->suspend,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_FSR_RESUME:
+		return kxtf9_set_fsr(mlsl_handle, pdata,
+				     &private_data->resume,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_MOT_THS:
+		return kxtf9_set_ths(mlsl_handle, pdata,
+				     &private_data->suspend,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_NMOT_THS:
+		return kxtf9_set_ths(mlsl_handle, pdata,
+				     &private_data->resume,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_MOT_DUR:
+		return kxtf9_set_dur(mlsl_handle, pdata,
+				     &private_data->suspend,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_NMOT_DUR:
+		return kxtf9_set_dur(mlsl_handle, pdata,
+				     &private_data->resume,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
+		return kxtf9_set_irq(mlsl_handle, pdata,
+				     &private_data->suspend,
+				     data->apply, *((long *)data->data));
+	case MPU_SLAVE_CONFIG_IRQ_RESUME:
+		return kxtf9_set_irq(mlsl_handle, pdata,
+				     &private_data->resume,
+				     data->apply, *((long *)data->data));
+	default:
+		return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
+	};
+
+	return INV_SUCCESS;
+}
+
+static int kxtf9_get_config(void *mlsl_handle,
+			    struct ext_slave_descr *slave,
+			    struct ext_slave_platform_data *pdata,
+			    struct ext_slave_config *data)
+{
+	struct kxtf9_private_data *private_data = pdata->private_data;
+	if (!data->data)
+		return INV_ERROR_INVALID_PARAMETER;
+
+	switch (data->key) {
+	case MPU_SLAVE_CONFIG_ODR_SUSPEND:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->suspend.odr;
+		break;
+	case MPU_SLAVE_CONFIG_ODR_RESUME:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->resume.odr;
+		break;
+	case MPU_SLAVE_CONFIG_FSR_SUSPEND:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->suspend.fsr;
+		break;
+	case MPU_SLAVE_CONFIG_FSR_RESUME:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->resume.fsr;
+		break;
+	case MPU_SLAVE_CONFIG_MOT_THS:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->suspend.ths;
+		break;
+	case MPU_SLAVE_CONFIG_NMOT_THS:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->resume.ths;
+		break;
+	case MPU_SLAVE_CONFIG_MOT_DUR:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->suspend.dur;
+		break;
+	case MPU_SLAVE_CONFIG_NMOT_DUR:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->resume.dur;
+		break;
+	case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->suspend.irq_type;
+		break;
+	case MPU_SLAVE_CONFIG_IRQ_RESUME:
+		(*(unsigned long *)data->data) =
+		    (unsigned long)private_data->resume.irq_type;
+		break;
+	default:
+		return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
+	};
+
+	return INV_SUCCESS;
+}
+
+static int kxtf9_read(void *mlsl_handle,
+		      struct ext_slave_descr *slave,
+		      struct ext_slave_platform_data *pdata,
+		      unsigned char *data)
+{
+	int result;
+	unsigned char reg;
+	result = inv_serial_read(mlsl_handle, pdata->address,
+				 KXTF9_INT_SRC_REG2, 1, &reg);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	if (!(reg & 0x10))
+		return INV_ERROR_ACCEL_DATA_NOT_READY;
+
+	result = inv_serial_read(mlsl_handle, pdata->address,
+				 slave->read_reg, slave->read_len, data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	return result;
+}
+
+static struct ext_slave_descr kxtf9_descr = {
+	.init             = kxtf9_init,
+	.exit             = kxtf9_exit,
+	.suspend          = kxtf9_suspend,
+	.resume           = kxtf9_resume,
+	.read             = kxtf9_read,
+	.config           = kxtf9_config,
+	.get_config       = kxtf9_get_config,
+	.name             = "kxtf9",
+	.type             = EXT_SLAVE_TYPE_ACCEL,
+	.id               = ACCEL_ID_KXTF9,
+	.read_reg         = 0x06,
+	.read_len         = 6,
+	.endian           = EXT_SLAVE_LITTLE_ENDIAN,
+	.range            = {2, 0},
+	.trigger          = NULL,
+};
+
+static
+struct ext_slave_descr *kxtf9_get_slave_descr(void)
+{
+	return &kxtf9_descr;
+}
+
+/* -------------------------------------------------------------------------- */
+struct kxtf9_mod_private_data {
+	struct i2c_client *client;
+	struct ext_slave_platform_data *pdata;
+};
+
+static unsigned short normal_i2c[] = { I2C_CLIENT_END };
+
+static int kxtf9_mod_probe(struct i2c_client *client,
+			   const struct i2c_device_id *devid)
+{
+	struct ext_slave_platform_data *pdata;
+	struct kxtf9_mod_private_data *private_data;
+	int result = 0;
+
+	dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		result = -ENODEV;
+		goto out_no_free;
+	}
+
+	pdata = client->dev.platform_data;
+	if (!pdata) {
+		dev_err(&client->adapter->dev,
+			"Missing platform data for slave %s\n", devid->name);
+		result = -EFAULT;
+		goto out_no_free;
+	}
+
+	private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
+	if (!private_data) {
+		result = -ENOMEM;
+		goto out_no_free;
+	}
+
+	i2c_set_clientdata(client, private_data);
+	private_data->client = client;
+	private_data->pdata = pdata;
+
+	result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
+					kxtf9_get_slave_descr);
+	if (result) {
+		dev_err(&client->adapter->dev,
+			"Slave registration failed: %s, %d\n",
+			devid->name, result);
+		goto out_free_memory;
+	}
+
+	return result;
+
+out_free_memory:
+	kfree(private_data);
+out_no_free:
+	dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+	return result;
+
+}
+
+static int kxtf9_mod_remove(struct i2c_client *client)
+{
+	struct kxtf9_mod_private_data *private_data =
+		i2c_get_clientdata(client);
+
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+
+	inv_mpu_unregister_slave(client, private_data->pdata,
+				kxtf9_get_slave_descr);
+
+	kfree(private_data);
+	return 0;
+}
+
+static const struct i2c_device_id kxtf9_mod_id[] = {
+	{ "kxtf9", ACCEL_ID_KXTF9 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, kxtf9_mod_id);
+
+static struct i2c_driver kxtf9_mod_driver = {
+	.class = I2C_CLASS_HWMON,
+	.probe = kxtf9_mod_probe,
+	.remove = kxtf9_mod_remove,
+	.id_table = kxtf9_mod_id,
+	.driver = {
+		   .owner = THIS_MODULE,
+		   .name = "kxtf9_mod",
+		   },
+	.address_list = normal_i2c,
+};
+
+static int __init kxtf9_mod_init(void)
+{
+	int res = i2c_add_driver(&kxtf9_mod_driver);
+	pr_info("%s: Probe name %s\n", __func__, "kxtf9_mod");
+	if (res)
+		pr_err("%s failed\n", __func__);
+	return res;
+}
+
+static void __exit kxtf9_mod_exit(void)
+{
+	pr_info("%s\n", __func__);
+	i2c_del_driver(&kxtf9_mod_driver);
+}
+
+module_init(kxtf9_mod_init);
+module_exit(kxtf9_mod_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Driver to integrate KXTF9 sensor with the MPU");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("kxtf9_mod");
+
+/**
+ *  @}
+ */
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 10/11] misc: Add slave driver for ak8975 compass driver
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (7 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 09/11] misc: Add slave driver for kxtf9 accelerometer Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  2:18 ` [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor Nathan Royer
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/Kconfig          |    1 +
 drivers/misc/inv_mpu/Makefile         |    1 +
 drivers/misc/inv_mpu/compass/Kconfig  |   21 ++
 drivers/misc/inv_mpu/compass/Makefile |    7 +
 drivers/misc/inv_mpu/compass/ak8975.c |  500 +++++++++++++++++++++++++++++++++
 5 files changed, 530 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/compass/Kconfig
 create mode 100644 drivers/misc/inv_mpu/compass/Makefile
 create mode 100644 drivers/misc/inv_mpu/compass/ak8975.c

diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
index 6116010..7d9f2c3 100644
--- a/drivers/misc/inv_mpu/Kconfig
+++ b/drivers/misc/inv_mpu/Kconfig
@@ -56,5 +56,6 @@ config MPU_SENSORS_MPU6050_GYRO
 endchoice
 
 source "drivers/misc/inv_mpu/accel/Kconfig"
+source "drivers/misc/inv_mpu/compass/Kconfig"
 
 endif #INV_SENSORS
diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile
index d2c2c9a..dc77a1b 100644
--- a/drivers/misc/inv_mpu/Makefile
+++ b/drivers/misc/inv_mpu/Makefile
@@ -17,3 +17,4 @@ mpu3050-objs += mldl_print_cfg.o
 EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
 
 obj-y			+= accel/
+obj-y			+= compass/
diff --git a/drivers/misc/inv_mpu/compass/Kconfig b/drivers/misc/inv_mpu/compass/Kconfig
new file mode 100644
index 0000000..f4106d8
--- /dev/null
+++ b/drivers/misc/inv_mpu/compass/Kconfig
@@ -0,0 +1,21 @@
+menuconfig INV_SENSORS_COMPASS
+	bool "Compass Slave Sensors"
+	default y
+	---help---
+	  Say Y here to get to see options for device drivers for various
+	  compasses. This option alone does not add any kernel code.
+
+	  If you say N, all options in this submenu will be skipped and disabled.
+
+if INV_SENSORS_COMPASS
+
+config MPU_SENSORS_AK8975
+	tristate "AKM ak8975"
+	help
+	  This enables support for the AKM ak8975 compass
+	  This support is for integration with the MPU3050 or MPU6050 gyroscope
+	  device driver.  Only one compass can be registered at a time.
+	  Specifying more that one compass in the board file will result
+	  in runtime errors.
+
+endif
diff --git a/drivers/misc/inv_mpu/compass/Makefile b/drivers/misc/inv_mpu/compass/Makefile
new file mode 100644
index 0000000..a529476
--- /dev/null
+++ b/drivers/misc/inv_mpu/compass/Makefile
@@ -0,0 +1,7 @@
+#
+# Compass Slaves MPUxxxx
+#
+obj-$(CONFIG_MPU_SENSORS_AK8975) += inv_mpu_ak8975.o
+inv_mpu_ak8975-objs +=	ak8975.o
+
+EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
diff --git a/drivers/misc/inv_mpu/compass/ak8975.c b/drivers/misc/inv_mpu/compass/ak8975.c
new file mode 100644
index 0000000..1d43d5a
--- /dev/null
+++ b/drivers/misc/inv_mpu/compass/ak8975.c
@@ -0,0 +1,500 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ *  @addtogroup COMPASSDL
+ *
+ *  @{
+ *      @file   ak8975.c
+ *      @brief  Magnetometer setup and handling methods for the AKM AK8975,
+ *              AKM AK8975B, and AKM AK8975C compass devices.
+ */
+
+/* -------------------------------------------------------------------------- */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include "mpu-dev.h"
+
+#include <log.h>
+#include <linux/mpu.h>
+#include "mlsl.h"
+#include "mldl_cfg.h"
+#undef MPL_LOG_TAG
+#define MPL_LOG_TAG "MPL-compass"
+
+/* -------------------------------------------------------------------------- */
+#define AK8975_REG_ST1  (0x02)
+#define AK8975_REG_HXL  (0x03)
+#define AK8975_REG_ST2  (0x09)
+
+#define AK8975_REG_CNTL (0x0A)
+#define AK8975_REG_ASAX (0x10)
+#define AK8975_REG_ASAY (0x11)
+#define AK8975_REG_ASAZ (0x12)
+
+#define AK8975_CNTL_MODE_POWER_DOWN         (0x00)
+#define AK8975_CNTL_MODE_SINGLE_MEASUREMENT (0x01)
+#define AK8975_CNTL_MODE_FUSE_ROM_ACCESS    (0x0f)
+
+/* -------------------------------------------------------------------------- */
+struct ak8975_config {
+	char asa[COMPASS_NUM_AXES];	/* axis sensitivity adjustment */
+};
+
+struct ak8975_private_data {
+	struct ak8975_config init;
+};
+
+/* -------------------------------------------------------------------------- */
+static int ak8975_init(void *mlsl_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata)
+{
+	int result;
+	unsigned char serial_data[COMPASS_NUM_AXES];
+
+	struct ak8975_private_data *private_data;
+	private_data = (struct ak8975_private_data *)
+	    kzalloc(sizeof(struct ak8975_private_data), GFP_KERNEL);
+
+	if (!private_data)
+		return INV_ERROR_MEMORY_EXAUSTED;
+
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 AK8975_REG_CNTL,
+					 AK8975_CNTL_MODE_POWER_DOWN);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	/* Wait at least 100us */
+	udelay(100);
+
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 AK8975_REG_CNTL,
+					 AK8975_CNTL_MODE_FUSE_ROM_ACCESS);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Wait at least 200us */
+	udelay(200);
+
+	result = inv_serial_read(mlsl_handle, pdata->address,
+				 AK8975_REG_ASAX,
+				 COMPASS_NUM_AXES, serial_data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	pdata->private_data = private_data;
+
+	private_data->init.asa[0] = serial_data[0];
+	private_data->init.asa[1] = serial_data[1];
+	private_data->init.asa[2] = serial_data[2];
+
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+					 AK8975_REG_CNTL,
+					 AK8975_CNTL_MODE_POWER_DOWN);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	udelay(100);
+	return INV_SUCCESS;
+}
+
+static int ak8975_exit(void *mlsl_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata)
+{
+	kfree(pdata->private_data);
+	return INV_SUCCESS;
+}
+
+static int ak8975_suspend(void *mlsl_handle,
+		   struct ext_slave_descr *slave,
+		   struct ext_slave_platform_data *pdata)
+{
+	int result = INV_SUCCESS;
+	result =
+	    inv_serial_single_write(mlsl_handle, pdata->address,
+				    AK8975_REG_CNTL,
+				    AK8975_CNTL_MODE_POWER_DOWN);
+	msleep(1);		/* wait at least 100us */
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	return result;
+}
+
+static int ak8975_resume(void *mlsl_handle,
+		  struct ext_slave_descr *slave,
+		  struct ext_slave_platform_data *pdata)
+{
+	int result = INV_SUCCESS;
+	result =
+	    inv_serial_single_write(mlsl_handle, pdata->address,
+				    AK8975_REG_CNTL,
+				    AK8975_CNTL_MODE_SINGLE_MEASUREMENT);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	return result;
+}
+
+static int ak8975_read(void *mlsl_handle,
+		struct ext_slave_descr *slave,
+		struct ext_slave_platform_data *pdata, unsigned char *data)
+{
+	unsigned char regs[8];
+	unsigned char *stat = &regs[0];
+	unsigned char *stat2 = &regs[7];
+	int result = INV_SUCCESS;
+	int status = INV_SUCCESS;
+
+	result =
+	    inv_serial_read(mlsl_handle, pdata->address, AK8975_REG_ST1,
+			    8, regs);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* Always return the data and the status registers */
+	memcpy(data, &regs[1], 6);
+	data[6] = regs[0];
+	data[7] = regs[7];
+
+	/*
+	 * ST : data ready -
+	 * Measurement has been completed and data is ready to be read.
+	 */
+	if (*stat & 0x01)
+		status = INV_SUCCESS;
+
+	/*
+	 * ST2 : data error -
+	 * occurs when data read is started outside of a readable period;
+	 * data read would not be correct.
+	 * Valid in continuous measurement mode only.
+	 * In single measurement mode this error should not occour but we
+	 * stil account for it and return an error, since the data would be
+	 * corrupted.
+	 * DERR bit is self-clearing when ST2 register is read.
+	 */
+	if (*stat2 & 0x04)
+		status = INV_ERROR_COMPASS_DATA_ERROR;
+	/*
+	 * ST2 : overflow -
+	 * the sum of the absolute values of all axis |X|+|Y|+|Z| < 2400uT.
+	 * This is likely to happen in presence of an external magnetic
+	 * disturbance; it indicates, the sensor data is incorrect and should
+	 * be ignored.
+	 * An error is returned.
+	 * HOFL bit clears when a new measurement starts.
+	 */
+	if (*stat2 & 0x08)
+		status = INV_ERROR_COMPASS_DATA_OVERFLOW;
+	/*
+	 * ST : overrun -
+	 * the previous sample was not fetched and lost.
+	 * Valid in continuous measurement mode only.
+	 * In single measurement mode this error should not occour and we
+	 * don't consider this condition an error.
+	 * DOR bit is self-clearing when ST2 or any meas. data register is
+	 * read.
+	 */
+	if (*stat & 0x02) {
+		/* status = INV_ERROR_COMPASS_DATA_UNDERFLOW; */
+		status = INV_SUCCESS;
+	}
+
+	/*
+	 * trigger next measurement if:
+	 *    - stat is non zero;
+	 *    - if stat is zero and stat2 is non zero.
+	 * Won't trigger if data is not ready and there was no error.
+	 */
+	if (*stat != 0x00 || *stat2 != 0x00) {
+		result = inv_serial_single_write(
+		    mlsl_handle, pdata->address,
+		    AK8975_REG_CNTL, AK8975_CNTL_MODE_SINGLE_MEASUREMENT);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+	}
+
+	return status;
+}
+
+static int ak8975_config(void *mlsl_handle,
+			 struct ext_slave_descr *slave,
+			 struct ext_slave_platform_data *pdata,
+			 struct ext_slave_config *data)
+{
+	int result;
+	if (!data->data)
+		return INV_ERROR_INVALID_PARAMETER;
+
+	switch (data->key) {
+	case MPU_SLAVE_WRITE_REGISTERS:
+		result = inv_serial_write(mlsl_handle, pdata->address,
+					  data->len,
+					  (unsigned char *)data->data);
+		if (result) {
+			LOG_RESULT_LOCATION(result);
+			return result;
+		}
+		break;
+	case MPU_SLAVE_CONFIG_ODR_SUSPEND:
+	case MPU_SLAVE_CONFIG_ODR_RESUME:
+	case MPU_SLAVE_CONFIG_FSR_SUSPEND:
+	case MPU_SLAVE_CONFIG_FSR_RESUME:
+	case MPU_SLAVE_CONFIG_MOT_THS:
+	case MPU_SLAVE_CONFIG_NMOT_THS:
+	case MPU_SLAVE_CONFIG_MOT_DUR:
+	case MPU_SLAVE_CONFIG_NMOT_DUR:
+	case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
+	case MPU_SLAVE_CONFIG_IRQ_RESUME:
+	default:
+		return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
+	};
+
+	return INV_SUCCESS;
+}
+
+static int ak8975_get_config(void *mlsl_handle,
+			     struct ext_slave_descr *slave,
+			     struct ext_slave_platform_data *pdata,
+			     struct ext_slave_config *data)
+{
+	struct ak8975_private_data *private_data = pdata->private_data;
+	int result;
+	if (!data->data)
+		return INV_ERROR_INVALID_PARAMETER;
+
+	switch (data->key) {
+	case MPU_SLAVE_READ_REGISTERS:
+		{
+			unsigned char *serial_data =
+			    (unsigned char *)data->data;
+			result =
+			    inv_serial_read(mlsl_handle, pdata->address,
+					    serial_data[0], data->len - 1,
+					    &serial_data[1]);
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+			break;
+		}
+	case MPU_SLAVE_READ_SCALE:
+		{
+			unsigned char *serial_data =
+			    (unsigned char *)data->data;
+			serial_data[0] = private_data->init.asa[0];
+			serial_data[1] = private_data->init.asa[1];
+			serial_data[2] = private_data->init.asa[2];
+			result = INV_SUCCESS;
+			if (result) {
+				LOG_RESULT_LOCATION(result);
+				return result;
+			}
+			break;
+		}
+	case MPU_SLAVE_CONFIG_ODR_SUSPEND:
+		(*(unsigned long *)data->data) = 0;
+		break;
+	case MPU_SLAVE_CONFIG_ODR_RESUME:
+		(*(unsigned long *)data->data) = 8000;
+		break;
+	case MPU_SLAVE_CONFIG_FSR_SUSPEND:
+	case MPU_SLAVE_CONFIG_FSR_RESUME:
+	case MPU_SLAVE_CONFIG_MOT_THS:
+	case MPU_SLAVE_CONFIG_NMOT_THS:
+	case MPU_SLAVE_CONFIG_MOT_DUR:
+	case MPU_SLAVE_CONFIG_NMOT_DUR:
+	case MPU_SLAVE_CONFIG_IRQ_SUSPEND:
+	case MPU_SLAVE_CONFIG_IRQ_RESUME:
+	default:
+		return INV_ERROR_FEATURE_NOT_IMPLEMENTED;
+	};
+
+	return INV_SUCCESS;
+}
+
+static struct ext_slave_read_trigger ak8975_read_trigger = {
+	/*.reg              = */ 0x0A,
+	/*.value            = */ 0x01
+};
+
+static struct ext_slave_descr ak8975_descr = {
+	.init             = ak8975_init,
+	.exit             = ak8975_exit,
+	.suspend          = ak8975_suspend,
+	.resume           = ak8975_resume,
+	.read             = ak8975_read,
+	.config           = ak8975_config,
+	.get_config       = ak8975_get_config,
+	.name             = "ak8975",
+	.type             = EXT_SLAVE_TYPE_COMPASS,
+	.id               = COMPASS_ID_AK8975,
+	.read_reg         = 0x01,
+	.read_len         = 9,
+	.endian           = EXT_SLAVE_LITTLE_ENDIAN,
+	.range            = {9830, 4000},
+	.trigger          = &ak8975_read_trigger,
+};
+
+static
+struct ext_slave_descr *ak8975_get_slave_descr(void)
+{
+	return &ak8975_descr;
+}
+
+/* -------------------------------------------------------------------------- */
+struct ak8975_mod_private_data {
+	struct i2c_client *client;
+	struct ext_slave_platform_data *pdata;
+};
+
+static unsigned short normal_i2c[] = { I2C_CLIENT_END };
+
+static int ak8975_mod_probe(struct i2c_client *client,
+			   const struct i2c_device_id *devid)
+{
+	struct ext_slave_platform_data *pdata;
+	struct ak8975_mod_private_data *private_data;
+	int result = 0;
+
+	dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		result = -ENODEV;
+		goto out_no_free;
+	}
+
+	pdata = client->dev.platform_data;
+	if (!pdata) {
+		dev_err(&client->adapter->dev,
+			"Missing platform data for slave %s\n", devid->name);
+		result = -EFAULT;
+		goto out_no_free;
+	}
+
+	private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
+	if (!private_data) {
+		result = -ENOMEM;
+		goto out_no_free;
+	}
+
+	i2c_set_clientdata(client, private_data);
+	private_data->client = client;
+	private_data->pdata = pdata;
+
+	result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
+					ak8975_get_slave_descr);
+	if (result) {
+		dev_err(&client->adapter->dev,
+			"Slave registration failed: %s, %d\n",
+			devid->name, result);
+		goto out_free_memory;
+	}
+
+	return result;
+
+out_free_memory:
+	kfree(private_data);
+out_no_free:
+	dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+	return result;
+
+}
+
+static int ak8975_mod_remove(struct i2c_client *client)
+{
+	struct ak8975_mod_private_data *private_data =
+		i2c_get_clientdata(client);
+
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+	inv_mpu_unregister_slave(client, private_data->pdata,
+				ak8975_get_slave_descr);
+
+	kfree(private_data);
+	return 0;
+}
+
+static const struct i2c_device_id ak8975_mod_id[] = {
+	{ "ak8975", COMPASS_ID_AK8975 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, ak8975_mod_id);
+
+static struct i2c_driver ak8975_mod_driver = {
+	.class = I2C_CLASS_HWMON,
+	.probe = ak8975_mod_probe,
+	.remove = ak8975_mod_remove,
+	.id_table = ak8975_mod_id,
+	.driver = {
+		   .owner = THIS_MODULE,
+		   .name = "ak8975_mod",
+		   },
+	.address_list = normal_i2c,
+};
+
+static int __init ak8975_mod_init(void)
+{
+	int res = i2c_add_driver(&ak8975_mod_driver);
+	pr_info("%s: Probe name %s\n", __func__, "ak8975_mod");
+	if (res)
+		pr_err("%s failed\n", __func__);
+	return res;
+}
+
+static void __exit ak8975_mod_exit(void)
+{
+	pr_info("%s\n", __func__);
+	i2c_del_driver(&ak8975_mod_driver);
+}
+
+module_init(ak8975_mod_init);
+module_exit(ak8975_mod_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Driver to integrate AK8975 sensor with the MPU");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ak8975_mod");
+
+/**
+ *  @}
+ */
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (8 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 10/11] misc: Add slave driver for ak8975 compass driver Nathan Royer
@ 2011-07-01  2:18 ` Nathan Royer
  2011-07-01  7:56   ` Alan Cox
  2011-07-01  9:05   ` Jonathan Cameron
  2011-07-01  3:09 ` [PATCH 01/11] misc: inv_mpu primary header file and README file Greg KH
                   ` (4 subsequent siblings)
  14 siblings, 2 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01  2:18 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

Signed-off-by: Nathan Royer <nroyer@invensense.com>
---
 drivers/misc/inv_mpu/Kconfig           |    1 +
 drivers/misc/inv_mpu/Makefile          |    1 +
 drivers/misc/inv_mpu/pressure/Kconfig  |   20 ++
 drivers/misc/inv_mpu/pressure/Makefile |    8 +
 drivers/misc/inv_mpu/pressure/bma085.c |  367 ++++++++++++++++++++++++++++++++
 5 files changed, 397 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/inv_mpu/pressure/Kconfig
 create mode 100644 drivers/misc/inv_mpu/pressure/Makefile
 create mode 100644 drivers/misc/inv_mpu/pressure/bma085.c

diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
index 7d9f2c3..ea7d754 100644
--- a/drivers/misc/inv_mpu/Kconfig
+++ b/drivers/misc/inv_mpu/Kconfig
@@ -57,5 +57,6 @@ endchoice
 
 source "drivers/misc/inv_mpu/accel/Kconfig"
 source "drivers/misc/inv_mpu/compass/Kconfig"
+source "drivers/misc/inv_mpu/pressure/Kconfig"
 
 endif #INV_SENSORS
diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile
index dc77a1b..506b8f5 100644
--- a/drivers/misc/inv_mpu/Makefile
+++ b/drivers/misc/inv_mpu/Makefile
@@ -18,3 +18,4 @@ EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
 
 obj-y			+= accel/
 obj-y			+= compass/
+obj-y			+= pressure/
diff --git a/drivers/misc/inv_mpu/pressure/Kconfig b/drivers/misc/inv_mpu/pressure/Kconfig
new file mode 100644
index 0000000..f1c021e
--- /dev/null
+++ b/drivers/misc/inv_mpu/pressure/Kconfig
@@ -0,0 +1,20 @@
+menuconfig: INV_SENSORS_PRESSURE
+	bool "Pressure Sensor Slaves"
+	depends on INV_SENSORS
+	default y
+	help
+	  Select y to see a list of supported pressure sensors that can be
+	  integrated with the MPUxxxx set of motion processors.
+
+if INV_SENSORS_PRESSURE
+
+config MPU_SENSORS_BMA085
+	tristate "Bosch BMA085"
+	help
+	  This enables support for the Bosch bma085 pressure sensor
+	  This support is for integration with the MPU3050 or MPU6050 gyroscope
+          device driver.  Only one accelerometer can be registered at a time.
+	  Specifying more that one accelerometer in the board file will result
+	  in runtime errors.
+
+endif
diff --git a/drivers/misc/inv_mpu/pressure/Makefile b/drivers/misc/inv_mpu/pressure/Makefile
new file mode 100644
index 0000000..595923d
--- /dev/null
+++ b/drivers/misc/inv_mpu/pressure/Makefile
@@ -0,0 +1,8 @@
+#
+# Pressure Slaves to MPUxxxx
+#
+obj-$(CONFIG_MPU_SENSORS_BMA085) += inv_mpu_bma085.o
+inv_mpu_bma085-objs +=	bma085.o
+
+EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
+EXTRA_CFLAGS += -D__C99_DESIGNATED_INITIALIZER
diff --git a/drivers/misc/inv_mpu/pressure/bma085.c b/drivers/misc/inv_mpu/pressure/bma085.c
new file mode 100644
index 0000000..696d2b6
--- /dev/null
+++ b/drivers/misc/inv_mpu/pressure/bma085.c
@@ -0,0 +1,367 @@
+/*
+	$License:
+	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <http://www.gnu.org/licenses/>.
+	$
+ */
+
+/**
+ *  @defgroup   ACCELDL (Motion Library - Pressure Driver Layer)
+ *  @brief      Provides the interface to setup and handle a pressure
+ *              connected to the secondary I2C interface of the gyroscope.
+ *
+ *  @{
+ *      @file   bma085.c
+ *      @brief  Pressure setup and handling methods.
+ */
+
+/* ------------------ */
+/* - Include Files. - */
+/* ------------------ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include "mpu-dev.h"
+
+#include <linux/mpu.h>
+#include "mlsl.h"
+#include "log.h"
+
+/*
+ * this structure holds all device specific calibration parameters
+ */
+struct bmp085_calibration_param_t {
+	short ac1;
+	short ac2;
+	short ac3;
+	unsigned short ac4;
+	unsigned short ac5;
+	unsigned short ac6;
+	short b1;
+	short b2;
+	short mb;
+	short mc;
+	short md;
+	long param_b5;
+};
+
+struct bmp085_calibration_param_t cal_param;
+
+#define PRESSURE_BMA085_PARAM_MG      3038        /* calibration parameter */
+#define PRESSURE_BMA085_PARAM_MH     -7357        /* calibration parameter */
+#define PRESSURE_BMA085_PARAM_MI      3791        /* calibration parameter */
+
+/*********************************************
+ *    Pressure Initialization Functions
+ *********************************************/
+
+static int bma085_suspend(void *mlsl_handle,
+			  struct ext_slave_descr *slave,
+			  struct ext_slave_platform_data *pdata)
+{
+	int result = INV_SUCCESS;
+	return result;
+}
+
+#define PRESSURE_BMA085_PROM_START_ADDR  (0xAA)
+#define PRESSURE_BMA085_PROM_DATA_LEN    (22)
+#define PRESSURE_BMP085_CTRL_MEAS_REG    (0xF4)
+/* temperature measurent */
+#define PRESSURE_BMP085_T_MEAS           (0x2E)
+/* pressure measurement; oversampling_setting */
+#define PRESSURE_BMP085_P_MEAS_OSS_0     (0x34)
+#define PRESSURE_BMP085_P_MEAS_OSS_1     (0x74)
+#define PRESSURE_BMP085_P_MEAS_OSS_2     (0xB4)
+#define PRESSURE_BMP085_P_MEAS_OSS_3     (0xF4)
+#define PRESSURE_BMP085_ADC_OUT_MSB_REG  (0xF6)
+#define PRESSURE_BMP085_ADC_OUT_LSB_REG  (0xF7)
+
+static int bma085_resume(void *mlsl_handle,
+			 struct ext_slave_descr *slave,
+			 struct ext_slave_platform_data *pdata)
+{
+	int result;
+	unsigned char data[PRESSURE_BMA085_PROM_DATA_LEN];
+
+	result =
+	    inv_serial_read(mlsl_handle, pdata->address,
+			   PRESSURE_BMA085_PROM_START_ADDR,
+			   PRESSURE_BMA085_PROM_DATA_LEN, data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+
+	/* parameters AC1-AC6 */
+	cal_param.ac1 = (data[0] << 8) | data[1];
+	cal_param.ac2 = (data[2] << 8) | data[3];
+	cal_param.ac3 = (data[4] << 8) | data[5];
+	cal_param.ac4 = (data[6] << 8) | data[7];
+	cal_param.ac5 = (data[8] << 8) | data[9];
+	cal_param.ac6 = (data[10] << 8) | data[11];
+
+	/* parameters B1,B2 */
+	cal_param.b1 = (data[12] << 8) | data[13];
+	cal_param.b2 = (data[14] << 8) | data[15];
+
+	/* parameters MB,MC,MD */
+	cal_param.mb = (data[16] << 8) | data[17];
+	cal_param.mc = (data[18] << 8) | data[19];
+	cal_param.md = (data[20] << 8) | data[21];
+
+	return result;
+}
+
+static int bma085_read(void *mlsl_handle,
+		       struct ext_slave_descr *slave,
+		       struct ext_slave_platform_data *pdata,
+		       unsigned char *data)
+{
+	int result;
+	long pressure, x1, x2, x3, b3, b6;
+	unsigned long b4, b7;
+	unsigned long up;
+	unsigned short ut;
+	short oversampling_setting = 0;
+	short temperature;
+	long divisor;
+
+	/* get temprature */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+				       PRESSURE_BMP085_CTRL_MEAS_REG,
+				       PRESSURE_BMP085_T_MEAS);
+	msleep(5);
+	result =
+	    inv_serial_read(mlsl_handle, pdata->address,
+			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
+			   (unsigned char *)data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	ut = (data[0] << 8) | data[1];
+
+	x1 = (((long) ut - (long)cal_param.ac6) * (long)cal_param.ac5) >> 15;
+	divisor = x1 + cal_param.md;
+	if (!divisor)
+		return INV_ERROR_DIVIDE_BY_ZERO;
+
+	x2 = ((long)cal_param.mc << 11) / (x1 + cal_param.md);
+	cal_param.param_b5 = x1 + x2;
+	/* temperature in 0.1 degree C */
+	temperature = (short)((cal_param.param_b5 + 8) >> 4);
+
+	/* get pressure */
+	result = inv_serial_single_write(mlsl_handle, pdata->address,
+				       PRESSURE_BMP085_CTRL_MEAS_REG,
+				       PRESSURE_BMP085_P_MEAS_OSS_0);
+	msleep(5);
+	result =
+	    inv_serial_read(mlsl_handle, pdata->address,
+			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
+			   (unsigned char *)data);
+	if (result) {
+		LOG_RESULT_LOCATION(result);
+		return result;
+	}
+	up = (((unsigned long) data[0] << 8) | ((unsigned long) data[1]));
+
+	b6 = cal_param.param_b5 - 4000;
+	/* calculate B3 */
+	x1 = (b6*b6) >> 12;
+	x1 *= cal_param.b2;
+	x1 >>= 11;
+
+	x2 = (cal_param.ac2*b6);
+	x2 >>= 11;
+
+	x3 = x1 + x2;
+
+	b3 = (((((long)cal_param.ac1) * 4 + x3)
+	    << oversampling_setting) + 2) >> 2;
+
+	/* calculate B4 */
+	x1 = (cal_param.ac3 * b6) >> 13;
+	x2 = (cal_param.b1 * ((b6*b6) >> 12)) >> 16;
+	x3 = ((x1 + x2) + 2) >> 2;
+	b4 = (cal_param.ac4 * (unsigned long) (x3 + 32768)) >> 15;
+	if (!b4)
+		return INV_ERROR;
+
+	b7 = ((unsigned long)(up - b3) * (50000>>oversampling_setting));
+	if (b7 < 0x80000000)
+		pressure = (b7 << 1) / b4;
+	else
+		pressure = (b7 / b4) << 1;
+
+	x1 = pressure >> 8;
+	x1 *= x1;
+	x1 = (x1 * PRESSURE_BMA085_PARAM_MG) >> 16;
+	x2 = (pressure * PRESSURE_BMA085_PARAM_MH) >> 16;
+	/* pressure in Pa */
+	pressure += (x1 + x2 + PRESSURE_BMA085_PARAM_MI) >> 4;
+
+	data[0] = (unsigned char)(pressure >> 16);
+	data[1] = (unsigned char)(pressure >> 8);
+	data[2] = (unsigned char)(pressure & 0xFF);
+
+	return result;
+}
+
+static struct ext_slave_descr bma085_descr = {
+	.init             = NULL,
+	.exit             = NULL,
+	.suspend          = bma085_suspend,
+	.resume           = bma085_resume,
+	.read             = bma085_read,
+	.config           = NULL,
+	.get_config       = NULL,
+	.name             = "bma085",
+	.type             = EXT_SLAVE_TYPE_PRESSURE,
+	.id               = PRESSURE_ID_BMA085,
+	.read_reg         = 0xF6,
+	.read_len         = 3,
+	.endian           = EXT_SLAVE_BIG_ENDIAN,
+	.range            = {0, 0},
+};
+
+static
+struct ext_slave_descr *bma085_get_slave_descr(void)
+{
+	return &bma085_descr;
+}
+
+/* Platform data for the MPU */
+struct bma085_mod_private_data {
+	struct i2c_client *client;
+	struct ext_slave_platform_data *pdata;
+};
+
+static unsigned short normal_i2c[] = { I2C_CLIENT_END };
+
+static int bma085_mod_probe(struct i2c_client *client,
+			   const struct i2c_device_id *devid)
+{
+	struct ext_slave_platform_data *pdata;
+	struct bma085_mod_private_data *private_data;
+	int result = 0;
+
+	dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		result = -ENODEV;
+		goto out_no_free;
+	}
+
+	pdata = client->dev.platform_data;
+	if (!pdata) {
+		dev_err(&client->adapter->dev,
+			"Missing platform data for slave %s\n", devid->name);
+		result = -EFAULT;
+		goto out_no_free;
+	}
+
+	private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
+	if (!private_data) {
+		result = -ENOMEM;
+		goto out_no_free;
+	}
+
+	i2c_set_clientdata(client, private_data);
+	private_data->client = client;
+	private_data->pdata = pdata;
+
+	result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
+					bma085_get_slave_descr);
+	if (result) {
+		dev_err(&client->adapter->dev,
+			"Slave registration failed: %s, %d\n",
+			devid->name, result);
+		goto out_free_memory;
+	}
+
+	return result;
+
+out_free_memory:
+	kfree(private_data);
+out_no_free:
+	dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+	return result;
+
+}
+
+static int bma085_mod_remove(struct i2c_client *client)
+{
+	struct bma085_mod_private_data *private_data =
+		i2c_get_clientdata(client);
+
+	dev_dbg(&client->adapter->dev, "%s\n", __func__);
+
+	inv_mpu_unregister_slave(client, private_data->pdata,
+				bma085_get_slave_descr);
+
+	kfree(private_data);
+	return 0;
+}
+
+static const struct i2c_device_id bma085_mod_id[] = {
+	{ "bma085", PRESSURE_ID_BMA085 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, bma085_mod_id);
+
+static struct i2c_driver bma085_mod_driver = {
+	.class = I2C_CLASS_HWMON,
+	.probe = bma085_mod_probe,
+	.remove = bma085_mod_remove,
+	.id_table = bma085_mod_id,
+	.driver = {
+		   .owner = THIS_MODULE,
+		   .name = "bma085_mod",
+		   },
+	.address_list = normal_i2c,
+};
+
+static int __init bma085_mod_init(void)
+{
+	int res = i2c_add_driver(&bma085_mod_driver);
+	pr_info("%s: Probe name %s\n", __func__, "bma085_mod");
+	if (res)
+		pr_err("%s failed\n", __func__);
+	return res;
+}
+
+static void __exit bma085_mod_exit(void)
+{
+	pr_info("%s\n", __func__);
+	i2c_del_driver(&bma085_mod_driver);
+}
+
+module_init(bma085_mod_init);
+module_exit(bma085_mod_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Driver to integrate BMA085 sensor with the MPU");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("bma085_mod");
+/**
+ *  @}
+**/
-- 
1.7.4.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (9 preceding siblings ...)
  2011-07-01  2:18 ` [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor Nathan Royer
@ 2011-07-01  3:09 ` Greg KH
  2011-07-01  7:29   ` Alan Cox
  2011-07-01  9:00   ` Jonathan Cameron
  2011-07-01  3:59 ` Chris Wolfe
                   ` (3 subsequent siblings)
  14 siblings, 2 replies; 44+ messages in thread
From: Greg KH @ 2011-07-01  3:09 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Jonathan Cameron, Jiri Kosina, Alan Cox,
	Jean Delvare, linux-kernel, linux-input

On Thu, Jun 30, 2011 at 07:18:17PM -0700, Nathan Royer wrote:
> This files defines the userspace interface and how to integrate it into a
> platform.
> 
> Signed-off-by: Nathan Royer <nroyer@invensense.com>
> ---
> 
> This is the first of many patch files for the inv_mpu driver in its current
> state.  This is our first time submitting this driver, so I expect there to be
> a lot wrong with it, and expect to need to fix many things.
> 
> The inv_mpu driver attepts to implement a Motion Processing Unit interface.  As
> a unit, an accelerometer, magnetometer, gyroscope, and/or altimiter data is
> fused together to produce calibrated data and a quaternion.
> 
> The inv_mpu driver interface is currently implemented as a misc device, but may
> need to change to include both sysfs attributes and input devices.  I think
> that we will continue to need the ioctl interface, but many of the ioctls can
> be replace by attributes and/or input devices.
> 
> The mpu3050 has an i2c master interface designed to control an accelerometer
> and a Digital Motion Processor (DMP) used to perform sensor fusion on the
> gyroscope and accelerometer.  This data is then read out of the mpu3050 fifo
> and sent to userspace for distribution and optional propritary processing, such
> as fusion with a compass to produce a 9 axis quaternion.
> 
> Some question I have at the start are:
> 1) Is there a master design or standard interface for Motion Processing
> devices, specifically ones that do calibration, sensor fusion, and or have a
> micro-controller to do some of this work.
> 2) Is there a standard way to integrate user space components with kernel side
> components.
> 3) Should data be pushed back to the driver from userspace, and made available
> as an input device or should it remain as a character device.
> 4) Can a 4 element quaternion be added to input.h:
> ABS_QUATERNION_1 ABS_QUATERNION_I ABS_QUATERNION_J ABS_QUATERNION_K
> for <1, i, j, k>
> 5) Should we instead use a rotation vector as defined in the Android sensor:
> http://developer.android.com/reference/android/hardware/SensorEvent.html
> 6) Are there any other major design concerns?

Shouldn't you be using the iio subsystem for the kernel/user interface
as I think it handles most of this for you already, right?

> 7) Can an input device also have a character device interface for proprietary
> customization.

What do you mean by this?

greg k-h

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (10 preceding siblings ...)
  2011-07-01  3:09 ` [PATCH 01/11] misc: inv_mpu primary header file and README file Greg KH
@ 2011-07-01  3:59 ` Chris Wolfe
  2011-07-05 18:08   ` Nathan Royer
  2011-07-01  7:53 ` Alan Cox
                   ` (2 subsequent siblings)
  14 siblings, 1 reply; 44+ messages in thread
From: Chris Wolfe @ 2011-07-01  3:59 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

On Thu, Jun 30, 2011 at 7:18 PM, Nathan Royer <nroyer@invensense.com> wrote:
> The mpu3050 has an i2c master interface designed to control an accelerometer
> and a Digital Motion Processor (DMP) used to perform sensor fusion on the
> gyroscope and accelerometer.  This data is then read out of the mpu3050 fifo
> and sent to userspace for distribution and optional propritary processing, such
> as fusion with a compass to produce a 9 axis quaternion.

Sorry, just took a quick pass so I may be missing changes. Is this
driver to the stage where someone can practically initialize the 3050
and read low-level data without having the InvenSense MPL already
installed? (and without basically doing I2C reads and writes from
userspace)

As Greg K-H pointed out, IIO provides a lot of the support code for
this kind of driver. Have a look at drivers/staging/iio/imu and the
sibling directories. They provide a framework for reporting the
low-level data in a standard fashion, and some extra attributes to
load the DMP firmware would be easy to add.

I'm disconnected over the weekend, but will touch base with you and
Mike Housholder on Tuesday. Getting this upstream would be great, so
we ought to share notes.

Chris

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  3:09 ` [PATCH 01/11] misc: inv_mpu primary header file and README file Greg KH
@ 2011-07-01  7:29   ` Alan Cox
  2011-07-01  9:00   ` Jonathan Cameron
  1 sibling, 0 replies; 44+ messages in thread
From: Alan Cox @ 2011-07-01  7:29 UTC (permalink / raw)
  To: Greg KH
  Cc: Nathan Royer, Andrew Morton, Jonathan Cameron, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input

> Shouldn't you be using the iio subsystem for the kernel/user interface
> as I think it handles most of this for you already, right?

The input layer will for a general driver, in fact an mpu3050 driver
just went into the input layer although there are some other bits in
this one that perhaps will want adding to it.

Alan

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (11 preceding siblings ...)
  2011-07-01  3:59 ` Chris Wolfe
@ 2011-07-01  7:53 ` Alan Cox
  2011-07-01  9:08 ` Jonathan Cameron
  2011-07-01 21:04 ` Arnd Bergmann
  14 siblings, 0 replies; 44+ messages in thread
From: Alan Cox @ 2011-07-01  7:53 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input

> 1) Is there a master design or standard interface for Motion
> Processing devices, specifically ones that do calibration, sensor
> fusion, and or have a micro-controller to do some of this work.

Not specifically - but for the most part it shouldn't matter. We have
interfaces like request_firmware() to load firmware and we have
interfaces for input devices and for 

> 2) Is there a standard way to integrate user space components with
> kernel side components.

Use the standard kernel interfaces. If specific processing is needed in
user space the to go into the kernel it needs to be such that those
interfaces can be used in an open manner (this normally comes up with
3D graphics hardware rather than input), either by documenting the
interface or providing an open implementation.

> 3) Should data be pushed back to the driver from userspace, and made
> available as an input device or should it remain as a character
> device. 

The ektf2136 driver currently getting tidied up provides both. You can
open it either as an input driver directly, or you can access raw event
data in a documented for and do clever things with the data and feed it
back to the input layer via uevent.

> 6) Are there any other major design concerns? 

We have an initial mpu3050 driver which provides basic functionality
(from Wistron/Intel), normal Linux practice would be to extend and
improve that.

> 7) Can an input device also have a character device interface for
> proprietary customization.

That depends what you mean and what for. Fundamentally there is no
reason a device cannot present multiple interfaces although you might
need to lock between them.

> +Programming the chip using /dev/mpu
> +----------------------------------
> +Programming of 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 mpu.h.  Typically this is done by the
> mllite library in user +space.

Is this published with source or sufficient documentation for someone
to write their own ? Note btw the usual expectation would be that the
kernel driver uses request_firmware to load firmware as needed for that
part of the process.


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01  2:18 ` [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor Nathan Royer
@ 2011-07-01  7:56   ` Alan Cox
  2011-07-01  8:47     ` Jean Delvare
  2011-07-01 14:28     ` Chris Wolfe
  2011-07-01  9:05   ` Jonathan Cameron
  1 sibling, 2 replies; 44+ messages in thread
From: Alan Cox @ 2011-07-01  7:56 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input

The slave devices all appear to be generic i2c interface hardware. I
don't see why they are separate special slave devices to your driver,
they should simply be i²c drivers so they can be used when those
devices are found without the mpu3050.

Alan

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01  7:56   ` Alan Cox
@ 2011-07-01  8:47     ` Jean Delvare
  2011-07-01 14:28     ` Chris Wolfe
  1 sibling, 0 replies; 44+ messages in thread
From: Jean Delvare @ 2011-07-01  8:47 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Alan Cox, Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron,
	Jiri Kosina, linux-kernel, linux-input

On Fri, 1 Jul 2011 08:56:04 +0100, Alan Cox wrote:
> The slave devices all appear to be generic i2c interface hardware. I
> don't see why they are separate special slave devices to your driver,
> they should simply be i²c drivers so they can be used when those
> devices are found without the mpu3050.

Seconded. As a matter of fact, we already have a driver for the AK8975
magnetometer:
drivers/staging/iio/magnetometer/ak8975.c

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  3:09 ` [PATCH 01/11] misc: inv_mpu primary header file and README file Greg KH
  2011-07-01  7:29   ` Alan Cox
@ 2011-07-01  9:00   ` Jonathan Cameron
  1 sibling, 0 replies; 44+ messages in thread
From: Jonathan Cameron @ 2011-07-01  9:00 UTC (permalink / raw)
  To: Greg KH
  Cc: Nathan Royer, Andrew Morton, Jiri Kosina, Alan Cox, Jean Delvare,
	linux-kernel, linux-input, Torokhov

On 07/01/11 04:09, Greg KH wrote:
> On Thu, Jun 30, 2011 at 07:18:17PM -0700, Nathan Royer wrote:
>> This files defines the userspace interface and how to integrate it into a
>> platform.
>>
>> Signed-off-by: Nathan Royer <nroyer@invensense.com>
>> ---
>>
>> This is the first of many patch files for the inv_mpu driver in its current
>> state.  This is our first time submitting this driver, so I expect there to be
>> a lot wrong with it, and expect to need to fix many things.
>>
>> The inv_mpu driver attepts to implement a Motion Processing Unit interface.  As
>> a unit, an accelerometer, magnetometer, gyroscope, and/or altimiter data is
>> fused together to produce calibrated data and a quaternion.
>>
>> The inv_mpu driver interface is currently implemented as a misc device, but may
>> need to change to include both sysfs attributes and input devices.  I think
>> that we will continue to need the ioctl interface, but many of the ioctls can
>> be replace by attributes and/or input devices.
>>
>> The mpu3050 has an i2c master interface designed to control an accelerometer
>> and a Digital Motion Processor (DMP) used to perform sensor fusion on the
>> gyroscope and accelerometer.  This data is then read out of the mpu3050 fifo
>> and sent to userspace for distribution and optional propritary processing, such
>> as fusion with a compass to produce a 9 axis quaternion.
>>
>> Some question I have at the start are:
>> 1) Is there a master design or standard interface for Motion Processing
>> devices, specifically ones that do calibration, sensor fusion, and or have a
>> micro-controller to do some of this work.
Some of Analog's parts are doing a very basic form of this (bit of integration etc).
Ultimately in their case it is transparent, so they just look like additional data
channels.  Here it looks more sophisticated.

>> 2) Is there a standard way to integrate user space components with kernel side
>> components.
>> 3) Should data be pushed back to the driver from userspace, and made available
>> as an input device or should it remain as a character device.
Depends on the use case.  If you have to do userspace processing, then uinput
does this nicely.
>> 4) Can a 4 element quaternion be added to input.h:
>> ABS_QUATERNION_1 ABS_QUATERNION_I ABS_QUATERNION_J ABS_QUATERNION_K
>> for <1, i, j, k>
>> 5) Should we instead use a rotation vector as defined in the Android sensor:
>> http://developer.android.com/reference/android/hardware/SensorEvent.html
If you hardware is producing quaternions directly you are not going to want to
the necessary sin / cos in kernel. Trivial in userspace though.
>> 6) Are there any other major design concerns?
The big one Alan and Jean have commented on.  This device just has slave i2c devices
they should have their own drivers if at all possible.
> 
> Shouldn't you be using the iio subsystem for the kernel/user interface
> as I think it handles most of this for you already, right?
Few bits we haven't seen before, but nothing that can't be easily added.
We do have a usual question here of whether this is better as an input device
though. Dmitry, what is your view on this one?  Certainly doesn't want to end
up in misc.  Alan (in other branch) has highlighted an existing driver for the
mpu part.
> 
>> 7) Can an input device also have a character device interface for proprietary
>> customization.
> 
> What do you mean by this?
> 
> greg k-h
> 


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01  2:18 ` [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor Nathan Royer
  2011-07-01  7:56   ` Alan Cox
@ 2011-07-01  9:05   ` Jonathan Cameron
  2011-07-01 10:35     ` Manuel Stahl
  1 sibling, 1 reply; 44+ messages in thread
From: Jonathan Cameron @ 2011-07-01  9:05 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jiri Kosina, Alan Cox,
	Jean Delvare, linux-kernel, linux-input, Manuel Stahl


Hang on, so your motion processing unit takes in a pressure sensor?
Interesting....

Manuel, am I imagining things or did you have a driver for one of these?

Happy to take barometers in IIO.
> Signed-off-by: Nathan Royer <nroyer@invensense.com>
> ---
>  drivers/misc/inv_mpu/Kconfig           |    1 +
>  drivers/misc/inv_mpu/Makefile          |    1 +
>  drivers/misc/inv_mpu/pressure/Kconfig  |   20 ++
>  drivers/misc/inv_mpu/pressure/Makefile |    8 +
>  drivers/misc/inv_mpu/pressure/bma085.c |  367 ++++++++++++++++++++++++++++++++
>  5 files changed, 397 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/misc/inv_mpu/pressure/Kconfig
>  create mode 100644 drivers/misc/inv_mpu/pressure/Makefile
>  create mode 100644 drivers/misc/inv_mpu/pressure/bma085.c
> 
> diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
> index 7d9f2c3..ea7d754 100644
> --- a/drivers/misc/inv_mpu/Kconfig
> +++ b/drivers/misc/inv_mpu/Kconfig
> @@ -57,5 +57,6 @@ endchoice
>  
>  source "drivers/misc/inv_mpu/accel/Kconfig"
>  source "drivers/misc/inv_mpu/compass/Kconfig"
> +source "drivers/misc/inv_mpu/pressure/Kconfig"
>  
>  endif #INV_SENSORS
> diff --git a/drivers/misc/inv_mpu/Makefile b/drivers/misc/inv_mpu/Makefile
> index dc77a1b..506b8f5 100644
> --- a/drivers/misc/inv_mpu/Makefile
> +++ b/drivers/misc/inv_mpu/Makefile
> @@ -18,3 +18,4 @@ EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
>  
>  obj-y			+= accel/
>  obj-y			+= compass/
> +obj-y			+= pressure/
> diff --git a/drivers/misc/inv_mpu/pressure/Kconfig b/drivers/misc/inv_mpu/pressure/Kconfig
> new file mode 100644
> index 0000000..f1c021e
> --- /dev/null
> +++ b/drivers/misc/inv_mpu/pressure/Kconfig
> @@ -0,0 +1,20 @@
> +menuconfig: INV_SENSORS_PRESSURE
> +	bool "Pressure Sensor Slaves"
> +	depends on INV_SENSORS
> +	default y
> +	help
> +	  Select y to see a list of supported pressure sensors that can be
> +	  integrated with the MPUxxxx set of motion processors.
> +
> +if INV_SENSORS_PRESSURE
> +
> +config MPU_SENSORS_BMA085
> +	tristate "Bosch BMA085"
> +	help
> +	  This enables support for the Bosch bma085 pressure sensor
> +	  This support is for integration with the MPU3050 or MPU6050 gyroscope
> +          device driver.  Only one accelerometer can be registered at a time.
> +	  Specifying more that one accelerometer in the board file will result
> +	  in runtime errors.
That's a seriously weird message for a pressure sensor...
> +
> +endif
> diff --git a/drivers/misc/inv_mpu/pressure/Makefile b/drivers/misc/inv_mpu/pressure/Makefile
> new file mode 100644
> index 0000000..595923d
> --- /dev/null
> +++ b/drivers/misc/inv_mpu/pressure/Makefile
> @@ -0,0 +1,8 @@
> +#
> +# Pressure Slaves to MPUxxxx
> +#
> +obj-$(CONFIG_MPU_SENSORS_BMA085) += inv_mpu_bma085.o
> +inv_mpu_bma085-objs +=	bma085.o
> +
> +EXTRA_CFLAGS += -Idrivers/misc/inv_mpu
> +EXTRA_CFLAGS += -D__C99_DESIGNATED_INITIALIZER
> diff --git a/drivers/misc/inv_mpu/pressure/bma085.c b/drivers/misc/inv_mpu/pressure/bma085.c
> new file mode 100644
> index 0000000..696d2b6
> --- /dev/null
> +++ b/drivers/misc/inv_mpu/pressure/bma085.c
> @@ -0,0 +1,367 @@
> +/*
> +	$License:
> +	Copyright (C) 2011 InvenSense Corporation, All Rights Reserved.
> +
> +	This program is free software; you can redistribute it and/or modify
> +	it under the terms of the GNU General Public License as published by
> +	the Free Software Foundation; either version 2 of the License, or
> +	(at your option) any later version.
> +
> +	This program is distributed in the hope that it will be useful,
> +	but WITHOUT ANY WARRANTY; without even the implied warranty of
> +	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +	GNU General Public License for more details.
> +
> +	You should have received a copy of the GNU General Public License
> +	along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +	$
> + */
> +
> +/**
> + *  @defgroup   ACCELDL (Motion Library - Pressure Driver Layer)
> + *  @brief      Provides the interface to setup and handle a pressure
> + *              connected to the secondary I2C interface of the gyroscope.
> + *
> + *  @{
> + *      @file   bma085.c
> + *      @brief  Pressure setup and handling methods.
> + */
> +
> +/* ------------------ */
> +/* - Include Files. - */
> +/* ------------------ */
> +
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/kernel.h>
> +#include <linux/errno.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include "mpu-dev.h"
> +
> +#include <linux/mpu.h>
> +#include "mlsl.h"
> +#include "log.h"
> +
> +/*
> + * this structure holds all device specific calibration parameters
> + */
> +struct bmp085_calibration_param_t {
> +	short ac1;
> +	short ac2;
> +	short ac3;
> +	unsigned short ac4;
> +	unsigned short ac5;
> +	unsigned short ac6;
> +	short b1;
> +	short b2;
> +	short mb;
> +	short mc;
> +	short md;
> +	long param_b5;
> +};
> +
> +struct bmp085_calibration_param_t cal_param;
> +
> +#define PRESSURE_BMA085_PARAM_MG      3038        /* calibration parameter */
> +#define PRESSURE_BMA085_PARAM_MH     -7357        /* calibration parameter */
> +#define PRESSURE_BMA085_PARAM_MI      3791        /* calibration parameter */
> +
> +/*********************************************
> + *    Pressure Initialization Functions
> + *********************************************/
> +
> +static int bma085_suspend(void *mlsl_handle,
> +			  struct ext_slave_descr *slave,
> +			  struct ext_slave_platform_data *pdata)
> +{
> +	int result = INV_SUCCESS;
> +	return result;
> +}
> +
> +#define PRESSURE_BMA085_PROM_START_ADDR  (0xAA)
> +#define PRESSURE_BMA085_PROM_DATA_LEN    (22)
> +#define PRESSURE_BMP085_CTRL_MEAS_REG    (0xF4)
> +/* temperature measurent */
> +#define PRESSURE_BMP085_T_MEAS           (0x2E)
> +/* pressure measurement; oversampling_setting */
> +#define PRESSURE_BMP085_P_MEAS_OSS_0     (0x34)
> +#define PRESSURE_BMP085_P_MEAS_OSS_1     (0x74)
> +#define PRESSURE_BMP085_P_MEAS_OSS_2     (0xB4)
> +#define PRESSURE_BMP085_P_MEAS_OSS_3     (0xF4)
> +#define PRESSURE_BMP085_ADC_OUT_MSB_REG  (0xF6)
> +#define PRESSURE_BMP085_ADC_OUT_LSB_REG  (0xF7)
> +
> +static int bma085_resume(void *mlsl_handle,
> +			 struct ext_slave_descr *slave,
> +			 struct ext_slave_platform_data *pdata)
> +{
> +	int result;
> +	unsigned char data[PRESSURE_BMA085_PROM_DATA_LEN];
> +
> +	result =
> +	    inv_serial_read(mlsl_handle, pdata->address,
> +			   PRESSURE_BMA085_PROM_START_ADDR,
> +			   PRESSURE_BMA085_PROM_DATA_LEN, data);
> +	if (result) {
> +		LOG_RESULT_LOCATION(result);
> +		return result;
> +	}
> +
> +	/* parameters AC1-AC6 */
> +	cal_param.ac1 = (data[0] << 8) | data[1];
> +	cal_param.ac2 = (data[2] << 8) | data[3];
> +	cal_param.ac3 = (data[4] << 8) | data[5];
> +	cal_param.ac4 = (data[6] << 8) | data[7];
> +	cal_param.ac5 = (data[8] << 8) | data[9];
> +	cal_param.ac6 = (data[10] << 8) | data[11];
> +
> +	/* parameters B1,B2 */
> +	cal_param.b1 = (data[12] << 8) | data[13];
> +	cal_param.b2 = (data[14] << 8) | data[15];
> +
> +	/* parameters MB,MC,MD */
> +	cal_param.mb = (data[16] << 8) | data[17];
> +	cal_param.mc = (data[18] << 8) | data[19];
> +	cal_param.md = (data[20] << 8) | data[21];
> +
> +	return result;
> +}
> +
> +static int bma085_read(void *mlsl_handle,
> +		       struct ext_slave_descr *slave,
> +		       struct ext_slave_platform_data *pdata,
> +		       unsigned char *data)
> +{
> +	int result;
> +	long pressure, x1, x2, x3, b3, b6;
> +	unsigned long b4, b7;
> +	unsigned long up;
> +	unsigned short ut;
> +	short oversampling_setting = 0;
> +	short temperature;
> +	long divisor;
> +
> +	/* get temprature */
> +	result = inv_serial_single_write(mlsl_handle, pdata->address,
> +				       PRESSURE_BMP085_CTRL_MEAS_REG,
> +				       PRESSURE_BMP085_T_MEAS);
> +	msleep(5);
> +	result =
> +	    inv_serial_read(mlsl_handle, pdata->address,
> +			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
> +			   (unsigned char *)data);
> +	if (result) {
> +		LOG_RESULT_LOCATION(result);
> +		return result;
> +	}
> +	ut = (data[0] << 8) | data[1];
> +
> +	x1 = (((long) ut - (long)cal_param.ac6) * (long)cal_param.ac5) >> 15;
> +	divisor = x1 + cal_param.md;
> +	if (!divisor)
> +		return INV_ERROR_DIVIDE_BY_ZERO;
> +
> +	x2 = ((long)cal_param.mc << 11) / (x1 + cal_param.md);
> +	cal_param.param_b5 = x1 + x2;
> +	/* temperature in 0.1 degree C */
> +	temperature = (short)((cal_param.param_b5 + 8) >> 4);
> +
> +	/* get pressure */
> +	result = inv_serial_single_write(mlsl_handle, pdata->address,
> +				       PRESSURE_BMP085_CTRL_MEAS_REG,
> +				       PRESSURE_BMP085_P_MEAS_OSS_0);
> +	msleep(5);
> +	result =
> +	    inv_serial_read(mlsl_handle, pdata->address,
> +			   PRESSURE_BMP085_ADC_OUT_MSB_REG, 2,
> +			   (unsigned char *)data);
> +	if (result) {
> +		LOG_RESULT_LOCATION(result);
> +		return result;
> +	}
> +	up = (((unsigned long) data[0] << 8) | ((unsigned long) data[1]));
> +
> +	b6 = cal_param.param_b5 - 4000;
> +	/* calculate B3 */
> +	x1 = (b6*b6) >> 12;
> +	x1 *= cal_param.b2;
> +	x1 >>= 11;
> +
> +	x2 = (cal_param.ac2*b6);
> +	x2 >>= 11;
> +
> +	x3 = x1 + x2;
> +
> +	b3 = (((((long)cal_param.ac1) * 4 + x3)
> +	    << oversampling_setting) + 2) >> 2;
> +
> +	/* calculate B4 */
> +	x1 = (cal_param.ac3 * b6) >> 13;
> +	x2 = (cal_param.b1 * ((b6*b6) >> 12)) >> 16;
> +	x3 = ((x1 + x2) + 2) >> 2;
> +	b4 = (cal_param.ac4 * (unsigned long) (x3 + 32768)) >> 15;
> +	if (!b4)
> +		return INV_ERROR;
> +
> +	b7 = ((unsigned long)(up - b3) * (50000>>oversampling_setting));
> +	if (b7 < 0x80000000)
> +		pressure = (b7 << 1) / b4;
> +	else
> +		pressure = (b7 / b4) << 1;
> +
> +	x1 = pressure >> 8;
> +	x1 *= x1;
> +	x1 = (x1 * PRESSURE_BMA085_PARAM_MG) >> 16;
> +	x2 = (pressure * PRESSURE_BMA085_PARAM_MH) >> 16;
> +	/* pressure in Pa */
> +	pressure += (x1 + x2 + PRESSURE_BMA085_PARAM_MI) >> 4;
> +
> +	data[0] = (unsigned char)(pressure >> 16);
> +	data[1] = (unsigned char)(pressure >> 8);
> +	data[2] = (unsigned char)(pressure & 0xFF);
> +
> +	return result;
> +}
> +
> +static struct ext_slave_descr bma085_descr = {
> +	.init             = NULL,
> +	.exit             = NULL,
> +	.suspend          = bma085_suspend,
> +	.resume           = bma085_resume,
> +	.read             = bma085_read,
> +	.config           = NULL,
> +	.get_config       = NULL,
> +	.name             = "bma085",
> +	.type             = EXT_SLAVE_TYPE_PRESSURE,
> +	.id               = PRESSURE_ID_BMA085,
> +	.read_reg         = 0xF6,
> +	.read_len         = 3,
> +	.endian           = EXT_SLAVE_BIG_ENDIAN,
> +	.range            = {0, 0},
> +};
> +
> +static
> +struct ext_slave_descr *bma085_get_slave_descr(void)
> +{
> +	return &bma085_descr;
> +}
> +
> +/* Platform data for the MPU */
> +struct bma085_mod_private_data {
> +	struct i2c_client *client;
> +	struct ext_slave_platform_data *pdata;
> +};
> +
> +static unsigned short normal_i2c[] = { I2C_CLIENT_END };
> +
> +static int bma085_mod_probe(struct i2c_client *client,
> +			   const struct i2c_device_id *devid)
> +{
> +	struct ext_slave_platform_data *pdata;
> +	struct bma085_mod_private_data *private_data;
> +	int result = 0;
> +
> +	dev_info(&client->adapter->dev, "%s: %s\n", __func__, devid->name);
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> +		result = -ENODEV;
> +		goto out_no_free;
> +	}
> +
> +	pdata = client->dev.platform_data;
> +	if (!pdata) {
> +		dev_err(&client->adapter->dev,
> +			"Missing platform data for slave %s\n", devid->name);
> +		result = -EFAULT;
> +		goto out_no_free;
> +	}
> +
> +	private_data = kzalloc(sizeof(*private_data), GFP_KERNEL);
> +	if (!private_data) {
> +		result = -ENOMEM;
> +		goto out_no_free;
> +	}
> +
> +	i2c_set_clientdata(client, private_data);
> +	private_data->client = client;
> +	private_data->pdata = pdata;
> +
> +	result = inv_mpu_register_slave(THIS_MODULE, client, pdata,
> +					bma085_get_slave_descr);
> +	if (result) {
> +		dev_err(&client->adapter->dev,
> +			"Slave registration failed: %s, %d\n",
> +			devid->name, result);
> +		goto out_free_memory;
> +	}
> +
> +	return result;
> +
> +out_free_memory:
> +	kfree(private_data);
> +out_no_free:
> +	dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
> +	return result;
> +
> +}
> +
> +static int bma085_mod_remove(struct i2c_client *client)
> +{
> +	struct bma085_mod_private_data *private_data =
> +		i2c_get_clientdata(client);
> +
> +	dev_dbg(&client->adapter->dev, "%s\n", __func__);
> +
> +	inv_mpu_unregister_slave(client, private_data->pdata,
> +				bma085_get_slave_descr);
> +
> +	kfree(private_data);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id bma085_mod_id[] = {
> +	{ "bma085", PRESSURE_ID_BMA085 },
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, bma085_mod_id);
> +
> +static struct i2c_driver bma085_mod_driver = {
> +	.class = I2C_CLASS_HWMON,
> +	.probe = bma085_mod_probe,
> +	.remove = bma085_mod_remove,
> +	.id_table = bma085_mod_id,
> +	.driver = {
> +		   .owner = THIS_MODULE,
> +		   .name = "bma085_mod",
> +		   },
> +	.address_list = normal_i2c,
> +};
> +
> +static int __init bma085_mod_init(void)
> +{
> +	int res = i2c_add_driver(&bma085_mod_driver);
> +	pr_info("%s: Probe name %s\n", __func__, "bma085_mod");
> +	if (res)
> +		pr_err("%s failed\n", __func__);
> +	return res;
> +}
> +
> +static void __exit bma085_mod_exit(void)
> +{
> +	pr_info("%s\n", __func__);
> +	i2c_del_driver(&bma085_mod_driver);
> +}
> +
> +module_init(bma085_mod_init);
> +module_exit(bma085_mod_exit);
> +
> +MODULE_AUTHOR("Invensense Corporation");
> +MODULE_DESCRIPTION("Driver to integrate BMA085 sensor with the MPU");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("bma085_mod");
> +/**
> + *  @}
> +**/


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (12 preceding siblings ...)
  2011-07-01  7:53 ` Alan Cox
@ 2011-07-01  9:08 ` Jonathan Cameron
  2011-07-01 16:39   ` Nathan Royer
  2011-07-01 21:04 ` Arnd Bergmann
  14 siblings, 1 reply; 44+ messages in thread
From: Jonathan Cameron @ 2011-07-01  9:08 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jiri Kosina, Alan Cox,
	Jean Delvare, linux-kernel, linux-input

On 07/01/11 03:18, Nathan Royer wrote:
> This files defines the userspace interface and how to integrate it into a
> platform.
> 
> Signed-off-by: Nathan Royer <nroyer@invensense.com>
> ---
> 
> This is the first of many patch files for the inv_mpu driver in its current
> state.  This is our first time submitting this driver, so I expect there to be
> a lot wrong with it, and expect to need to fix many things.
Just out of interest, can you give details of what other patches are to come?
I'm guessing more sensor drivers and I hate discovering I'm implementing drivers
for devices that someone already has working code for!

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01  9:05   ` Jonathan Cameron
@ 2011-07-01 10:35     ` Manuel Stahl
  0 siblings, 0 replies; 44+ messages in thread
From: Manuel Stahl @ 2011-07-01 10:35 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Nathan Royer, Andrew Morton, Greg Kroah-Hartman, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

On 01.07.2011 11:05, Jonathan Cameron wrote:
> Hang on, so your motion processing unit takes in a pressure sensor?
> Interesting....
>
> Manuel, am I imagining things or did you have a driver for one of these?
Yes, indeed we have one for bmp085. I have to update it for the latest 
API, but these are things I'm going to do during the next 2 month.

Regards,

-- 
Manuel Stahl
Fraunhofer-Institut IIS
Leistungsoptimierte Systeme

Nordostpark 93
D90411 Nürnberg
Telefon  +49 (0)911/58061-6419
Fax      +49 (0)911/58061-6398
E-Mail   manuel.stahl@iis.fraunhofer.de

http://www.iis.fraunhofer.de
http://www.smart-power.fraunhofer.de


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01  7:56   ` Alan Cox
  2011-07-01  8:47     ` Jean Delvare
@ 2011-07-01 14:28     ` Chris Wolfe
  2011-07-01 14:41       ` Alan Cox
  1 sibling, 1 reply; 44+ messages in thread
From: Chris Wolfe @ 2011-07-01 14:28 UTC (permalink / raw)
  To: Alan Cox
  Cc: Nathan Royer, Andrew Morton, Greg Kroah-Hartman,
	Jonathan Cameron, Jiri Kosina, Jean Delvare, linux-kernel,
	linux-input

On Fri, Jul 1, 2011 at 12:56 AM, Alan Cox <alan@linux.intel.com> wrote:
> The slave devices all appear to be generic i2c interface hardware. I
> don't see why they are separate special slave devices to your driver,
> they should simply be i²c drivers so they can be used when those
> devices are found without the mpu3050.

Just to field this quickly, since I've been playing with the hardware
involved. There are actually two different things handled by the
"slave devices".

The MPU-3050 (and some similar InvenSense parts) provide a secondary
I2C bus. By default this is passed through to the I2C bus on which the
3050 is installed, but the MPU can also detach the extra bus and act
as an I2C master on it. A slave device is connected on this extra bus,
configured normally by the kernel, and then taken over by the MPU
(which will periodically do a block read of a configured address).

The second case of a slave device is simply some other device that the
driver reads from, and passes the data to the MPU for processing.

Part of the reason I got started on the KXTF9 driver was to try and
understand how a normal kernel driver could cope with the "here and
gone again" behavior of being on the MPU's secondary bus.

The other note that may not have been made explicit yet is that the
3050 will happily provide unprocessed gyro and slave data without
firmware loaded. The firmware sets up some additional processing
capabilities of the chip, which are used by InvenSense's user-space
products.

Chris

[Sorry about gmail. I'm on the road and hope it doesn't mangle things
too badly.]

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01 14:28     ` Chris Wolfe
@ 2011-07-01 14:41       ` Alan Cox
  2011-07-01 15:52         ` Chris Wolfe
  2011-07-01 16:09         ` Jean Delvare
  0 siblings, 2 replies; 44+ messages in thread
From: Alan Cox @ 2011-07-01 14:41 UTC (permalink / raw)
  To: Chris Wolfe
  Cc: Alan Cox, Nathan Royer, Andrew Morton, Greg Kroah-Hartman,
	Jonathan Cameron, Jiri Kosina, Jean Delvare, linux-kernel,
	linux-input

> Just to field this quickly, since I've been playing with the hardware

(Ditto)

> Part of the reason I got started on the KXTF9 driver was to try and
> understand how a normal kernel driver could cope with the "here and
> gone again" behavior of being on the MPU's secondary bus.

It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
device. 

I would guess however that if you knew the device was going to be used for
the mpu3050 only you'd not add the "slave" devices in your platform data
in the first place as well so they didn't bounce in and out and you'd
probably teach the mpu3050 code to not create the bus if it was then
going to destroy it again a moment later.

> The other note that may not have been made explicit yet is that the
> 3050 will happily provide unprocessed gyro and slave data without
> firmware loaded. The firmware sets up some additional processing
> capabilities of the chip, which are used by InvenSense's user-space
> products.

Yes - and the current mpu3050 driver provides just these interfaces.

Of the other stuff the slave drivers seem to be no problem as standard
i²c bus devices, the MPU mode may depend upon whether there is open
documentation, applying the standards we apply to everything else. That
is "can someone write their own tools to use that interface based upon the
documentation/source code examples available".

Eg for the ektf driver which is pending the interface is all documented
and provides rectangles of pressure data but you'll have to go write
your own gesture recognizer parts if you want to do clever stuff with it.

Alan

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01 14:41       ` Alan Cox
@ 2011-07-01 15:52         ` Chris Wolfe
  2011-07-01 17:00           ` Alan Cox
  2011-07-01 16:09         ` Jean Delvare
  1 sibling, 1 reply; 44+ messages in thread
From: Chris Wolfe @ 2011-07-01 15:52 UTC (permalink / raw)
  To: Alan Cox
  Cc: Alan Cox, Nathan Royer, Andrew Morton, Greg Kroah-Hartman,
	Jonathan Cameron, Jiri Kosina, Jean Delvare, linux-kernel,
	linux-input

On Fri, Jul 1, 2011 at 7:41 AM, Alan Cox <alan@lxorguk.ukuu.org.uk> wrote:
>> Part of the reason I got started on the KXTF9 driver was to try and
>> understand how a normal kernel driver could cope with the "here and
>> gone again" behavior of being on the MPU's secondary bus.
>
> It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
> device.

Can such a hot pluggable device maintain state while "unplugged"?

e.g. if the system is going to suspend, we need to pause the MPU,
switch it back to pass-through mode, then send commands to suspend the
slave on that bus. I took that to mean we wanted the secondary bus to
have a normal device on it, which is still considered plugged in, but
temporarily inaccessible from the CPU.

> I would guess however that if you knew the device was going to be used for
> the mpu3050 only you'd not add the "slave" devices in your platform data
> in the first place as well so they didn't bounce in and out and you'd
> probably teach the mpu3050 code to not create the bus if it was then
> going to destroy it again a moment later.

Something needs to initialize and configure the slaved devices (and
suspend/resume/shutdown/etc). I'm not sure where this happens if not
A) special-purpose code in the mpu3050 driver for each possible slave;
or B) normal drivers and platform data which cope with B1) the kernel
relaying their data to the MPU and B2) being disconnected from the CPU
while the MPU reads from the device directly.

>> The other note that may not have been made explicit yet is that the
>> 3050 will happily provide unprocessed gyro and slave data without
>> firmware loaded. The firmware sets up some additional processing
>> capabilities of the chip, which are used by InvenSense's user-space
>> products.
>
> Yes - and the current mpu3050 driver provides just these interfaces.

Ah, great. Sorry for missing them. In scanning the ioctls, it looked
like dev-i2c was still a similar level of abstraction :)

Chris

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01 14:41       ` Alan Cox
  2011-07-01 15:52         ` Chris Wolfe
@ 2011-07-01 16:09         ` Jean Delvare
  1 sibling, 0 replies; 44+ messages in thread
From: Jean Delvare @ 2011-07-01 16:09 UTC (permalink / raw)
  To: Alan Cox, Chris Wolfe
  Cc: Alan Cox, Nathan Royer, Andrew Morton, Greg Kroah-Hartman,
	Jonathan Cameron, Jiri Kosina, linux-kernel, linux-input

On Fri, 1 Jul 2011 15:41:50 +0100, Alan Cox wrote:
> > Just to field this quickly, since I've been playing with the hardware
> 
> (Ditto)
> 
> > Part of the reason I got started on the KXTF9 driver was to try and
> > understand how a normal kernel driver could cope with the "here and
> > gone again" behavior of being on the MPU's secondary bus.
> 
> It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
> device. 
> 
> I would guess however that if you knew the device was going to be used for
> the mpu3050 only you'd not add the "slave" devices in your platform data
> in the first place as well so they didn't bounce in and out and you'd
> probably teach the mpu3050 code to not create the bus if it was then
> going to destroy it again a moment later.

I didn't look in the code in details, but I see no reason to destroy
buses. This is a standard multi-master case, where a given I2C bus
segment can be driven by two masters. If both the host I2C controller
and the MPU3050 master are fully I2C compliant, it should be no problem
to let both coexist. If you really need to handle mutual exclusion
(e.g. the MPU3050 implementation forces you to do so), you can leverage
the i2c multiplexing support for this, which is available since kernel
2.6.36. See drivers/i2c/muxes/pca9541.c for an example implementation.

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  9:08 ` Jonathan Cameron
@ 2011-07-01 16:39   ` Nathan Royer
  2011-07-03 11:29     ` Jonathan Cameron
  0 siblings, 1 reply; 44+ messages in thread
From: Nathan Royer @ 2011-07-01 16:39 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Andrew Morton, Greg Kroah-Hartman, Jiri Kosina, Alan Cox,
	Jean Delvare, linux-kernel, linux-input

> Just out of interest, can you give details of what other patches are to
> come?
> I'm guessing more sensor drivers and I hate discovering I'm
> implementing drivers
> for devices that someone already has working code for!

I wanted to start off with only 1 driver for each type of slave and with
only the MPU3050 and exclude the MPU6050 until we figure out exactly the
best way to fit this into the kernel.

The current full list of drivers we have developed in this architecture
are as follows:
MPU3050 and MPU6050 support in mldl_cfg.c
# sesnors - accel
accel/mpu6050.c
accel/kxsd9.c
accel/kxtf9.c
accel/bma150.c
accel/bma222.c
accel/bma250.c
accel/mma8450.c
accel/mma845x.c
accel/lis331.c
accel/lsm303a.c
accel/adxl34x.c
accel/lis3dh.c
# sensors - compass
compass/ak8975.c
compass/ak8972.c
compass/ami30x.c
compass/ami306.c
compass/hmc5883.c
compass/lsm303m.c
compass/yas529.c
compass/yas530.c
compass/mmc314x.c
compass/hscdtd002b.c
compass/hscdtd004a.c
# sensors - pressure
pressure/bma085.c

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01 15:52         ` Chris Wolfe
@ 2011-07-01 17:00           ` Alan Cox
  2011-07-01 17:56             ` Nathan Royer
  0 siblings, 1 reply; 44+ messages in thread
From: Alan Cox @ 2011-07-01 17:00 UTC (permalink / raw)
  To: Chris Wolfe
  Cc: Alan Cox, Nathan Royer, Andrew Morton, Greg Kroah-Hartman,
	Jonathan Cameron, Jiri Kosina, Jean Delvare, linux-kernel,
	linux-input

> > It's a hot pluggable bus - no different to an i2c bus on a hot pluggable
> > device.
> 
> Can such a hot pluggable device maintain state while "unplugged"?

The hardware or the driver ?

As far as the OS is concerned it would have gone away.

> e.g. if the system is going to suspend, we need to pause the MPU,
> switch it back to pass-through mode, then send commands to suspend the
> slave on that bus. I took that to mean we wanted the secondary bus to
> have a normal device on it, which is still considered plugged in, but
> temporarily inaccessible from the CPU.

Thats a bit messier but we'd still want them to be normal i²c drivers
when on the slave bus and being driven by Linux. Possibly you end up with
a little helper to send i²c commands directly to the bus  in the "smart"
mode.

> A) special-purpose code in the mpu3050 driver for each possible slave;
> or B) normal drivers and platform data which cope with B1) the kernel
> relaying their data to the MPU and B2) being disconnected from the CPU
> while the MPU reads from the device directly.

If it's operating as a normal i²c device then it'll behave like any
normal kernel device, and really ought to be using runtime pm anyway.

In "smart" mode its really up to the driver how it does it but two
versions of every device is not a good answer !

> 
> >> The other note that may not have been made explicit yet is that the
> >> 3050 will happily provide unprocessed gyro and slave data without
> >> firmware loaded. The firmware sets up some additional processing
> >> capabilities of the chip, which are used by InvenSense's user-space
> >> products.
> >
> > Yes - and the current mpu3050 driver provides just these interfaces.
> 
> Ah, great. Sorry for missing them. In scanning the ioctls, it looked
> like dev-i2c was still a similar level of abstraction :)

Sorry may not have been clear "just those" as in "just those which can be
used without loading firmware and running non-free stuff"

And really until that area is sorted it may be premature to worry about
the rest - if its going to need proprietary bits to use the smart mode,
then the kernel just needs an i2c slave bus implementation, a few drivers
(we have most listed already it seems), and we are done.

Alan

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 08/11] misc: Kconfig and Makefile changes for inv_mpu driver
  2011-07-01  2:18 ` [PATCH 08/11] misc: Kconfig and Makefile changes for inv_mpu driver Nathan Royer
@ 2011-07-01 17:10   ` Randy Dunlap
  0 siblings, 0 replies; 44+ messages in thread
From: Randy Dunlap @ 2011-07-01 17:10 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

On Thu, 30 Jun 2011 19:18:24 -0700 Nathan Royer wrote:

> Signed-off-by: Nathan Royer <nroyer@invensense.com>
> ---
>  drivers/misc/Kconfig          |    1 +
>  drivers/misc/Makefile         |    1 +
>  drivers/misc/inv_mpu/Kconfig  |   58 +++++++++++++++++++++++++++++++++++++++++
>  drivers/misc/inv_mpu/Makefile |   17 ++++++++++++
>  4 files changed, 77 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/misc/inv_mpu/Kconfig
>  create mode 100644 drivers/misc/inv_mpu/Makefile
> 
> diff --git a/drivers/misc/inv_mpu/Kconfig b/drivers/misc/inv_mpu/Kconfig
> new file mode 100644
> index 0000000..fdfa7a6
> --- /dev/null
> +++ b/drivers/misc/inv_mpu/Kconfig
> @@ -0,0 +1,58 @@
> +
> +menuconfig: INV_SENSORS

hm, that ':' there happens to work, but the syntax description for
"menuconfig" does not show it as allowed, and no other Kconfig files use
a colon there, so please drop it.

Oh, also not "default y", please.

> +	tristate "Motion Processing Unit"
> +	depends on I2C
> +	default y
> +
> +if INV_SENSORS
> +
> +choice
> +	prompt "MPU Master"
> +	depends on I2C && INV_SENSORS
> +	default MPU_SENSORS_MPU3050
> +
> +config MPU_SENSORS_MPU3050
> +	bool "MPU3050"
> +	depends on I2C
> +	select MPU_SENSORS_MPU3050_GYRO
> +	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 MPU_SENSORS_MPU6050A2
> +	bool "MPU6050A2"
> +	depends on I2C
> +	select MPU_SENSORS_MPU6050_GYRO
> +	help
> +	  If you say yes here you get support for the MPU6050A2 Gyroscope driver
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called mpu6050a2.
> +
> +config MPU_SENSORS_MPU6050B1
> +	bool "MPU6050B1"
> +	select MPU_SENSORS_MPU6050_GYRO
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for the MPU6050 Gyroscope driver
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called mpu6050b1.
> +
> +endchoice
> +
> +choice
> +	prompt "Gyroscope Type"
> +	depends on I2C && INV_SENSORS
> +	default MPU_SENSORS_MPU3050_GYRO
> +
> +config MPU_SENSORS_MPU3050_GYRO
> +	bool "MPU3050 built in gyroscope"
> +	depends on MPU_SENSORS_MPU3050
> +
> +config MPU_SENSORS_MPU6050_GYRO
> +	bool "MPU6050 built in gyroscope"
> +	depends on MPU_SENSORS_MPU6050B1 || MPU_SENSORS_MPU6050A2
> +
> +endchoice
> +
> +endif #INV_SENSORS



---
~Randy
*** Remember to use Documentation/SubmitChecklist when testing your code ***

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 05/11] misc: MPU3050 and slave device configuration.
  2011-07-01  2:18 ` [PATCH 05/11] misc: MPU3050 and slave device configuration Nathan Royer
@ 2011-07-01 17:55   ` Nathan Royer
  0 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01 17:55 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input, Chris Wolfe

Regarding the secondary slave, I've decided to explain it in more detail
in this patch thread since this is where the high level configuration
happens.  I've clipped a number of the details to highlight just the high
level activity.  For the 3050 master i2c, configuration only occurs with
the accelerometer, but with the 6050 (not included in this patch), it can
support both an accel (external or internal), and compass, and pressure.

The first function here sets the bypass mode.  When in bypass mode, the
device master I2C of the mpu3050 is off and the slave chip (accelerometer
for the 3050) can be accessed directly on the same bus as the mpu3050.
When enable is set to false, the slave is disconnected from the bus, and
at that point the only operation that can occur is for the mpu3050 master
i2c to do a block read into the memory of the DMP.  The DMP then makes
this data available through its FIFO after some processing with the gyro
data.

Because of the bypass functionality, we designed all acces to the slave to
be through the MPU driver instead of stand alone.  That way we can first
put the mpu3050 into bypass before calling any of the slave functions that
will communicate directly with the chip.  For example the config functions
with apply == false do not enable bypass mode.
> +/**
> + *  @brief  enables/disables the I2C bypass to an external device
> + *          connected to MPU's secondary I2C bus.
> + *  @param  enable
> + *              Non-zero to enable pass through.
> + *  @return INV_SUCCESS if successful, a non-zero error code
> otherwise.
> + */
> +static int mpu_set_i2c_bypass(struct mldl_cfg *mldl_cfg,
> +			      void *mlsl_handle, unsigned char enable)
> +{
> +	return mpu3050_set_i2c_bypass(mldl_cfg, mlsl_handle, enable);
> +}
> +

This next function sets up the before mentioned block read, by setting the
slave I2C address, the slave I2C starting register, and the size of the
read.

> +
> +static int mpu_set_slave(struct mldl_cfg *mldl_cfg,
> +			 void *gyro_handle,
> +			 struct ext_slave_descr *slave,
> +			 struct ext_slave_platform_data *slave_pdata,
> +			 int slave_id)
> +{
> +	return mpu_set_slave_mpu3050(mldl_cfg, gyro_handle, slave,
> +				     slave_pdata, slave_id);
> +}

The rest of the work happens in the suspend and resume functions:

> +int inv_mpu_resume(struct mldl_cfg *mldl_cfg,
> +		   void *gyro_handle,
> +		   void *accel_handle,
> +		   void *compass_handle,
> +		   void *pressure_handle,
> +		   unsigned long sensors)
> +{

...

> +	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
> +		if (!mldl_cfg->slave[ii] ||
> +		    !mldl_cfg->pdata_slave[ii] ||
> +		    !resume_slave[ii] ||
> +		    !(mldl_cfg->inv_mpu_state->status & (1 << ii)))
> +			continue;
> +

This loop is intended to also support the mpu6050 where multiple slaves
can be on the secondary bus, so each slave is checked to see if it is on
the secondary bus.  If it is, switch to bypass mode and resume the device.

> +		if (EXT_SLAVE_BUS_SECONDARY ==
> +		    mldl_cfg->pdata_slave[ii]->bus) {
> +			result = mpu_set_i2c_bypass(mldl_cfg, gyro_handle,
> +						    true);
> +			if (result) {
> +				LOG_RESULT_LOCATION(result);
> +				return result;
> +			}
> +		}
> +		result = mldl_cfg->slave[ii]->resume(slave_handle[ii],
> +						mldl_cfg->slave[ii],
> +
mldl_cfg->pdata_slave[ii]);
> +		if (result) {
> +			LOG_RESULT_LOCATION(result);
> +			return result;
> +		}
> +		mldl_cfg->inv_mpu_state->status &= ~(1 << ii);
> +	}

Here is where the slave is set up.

> +	for (ii = 0; ii < EXT_SLAVE_NUM_TYPES; ii++) {
> +		if (resume_dmp &&
> +		    !(mldl_cfg->inv_mpu_state->status & (1 << ii)) &&
> +		    mldl_cfg->pdata_slave[ii] &&
> +		    EXT_SLAVE_BUS_SECONDARY == mldl_cfg->pdata_slave[ii]-
> >bus) {
> +			result = mpu_set_slave(mldl_cfg,
> +					gyro_handle,
> +					mldl_cfg->slave[ii],
> +					mldl_cfg->pdata_slave[ii],
> +					mldl_cfg->slave[ii]->type);
> +			if (result) {
> +				LOG_RESULT_LOCATION(result);
> +				return result;
> +			}
> +		}
> +	}
> +
> +	/* Turn on the master i2c iterface if necessary */
> +	if (resume_dmp) {

Here is where the bypass mode removed if the DMP needs the data.  If this
is done access to the slave cannot occur unless the mpu3050 is first
bypassed.

> +		result = mpu_set_i2c_bypass(
> +			mldl_cfg, gyro_handle,
> +			!(mldl_cfg->inv_mpu_state->i2c_slaves_enabled));
> +		if (result) {
> +			LOG_RESULT_LOCATION(result);
> +			return result;
> +		}
> +
> +		/* Now start */
> +		result = dmp_start(mldl_cfg, gyro_handle);
> +		if (result) {
> +			LOG_RESULT_LOCATION(result);
> +			return result;
> +		}
> +	}
> +	mldl_cfg->inv_mpu_cfg->requested_sensors = sensors;
> +
> +	return result;
> +}

We weren't interested in standalone drivers at the time we started writing
the driver, but I think now is the time to start thinking about how to
merge the two sets of requirements.  We currently support each sensor in a
standalone fashion, but in our own unique way, I just need to figure out
the right way.

The feedback I've seen so far suggest that the iio layer is where this
driver belongs and how the standalone interface should look.  Today was
the first I heard of the iio layer, so I have some significant research to
do on how to do this.  From my brief look this morning, it looks like
there is a lot of stuff already in place that we can make use of.

Again, everybody thank you for the feedback.  Very helpful!

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor
  2011-07-01 17:00           ` Alan Cox
@ 2011-07-01 17:56             ` Nathan Royer
  0 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-01 17:56 UTC (permalink / raw)
  To: Alan Cox, Chris Wolfe
  Cc: Alan Cox, Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron,
	Jiri Kosina, Jean Delvare, linux-kernel, linux-input

I've added an explanation of some of the high level bypass mode work which
is done in mldl_cfg.c in response that patch

[PATCH 05/11] misc: MPU3050 and slave device configuration.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
                   ` (13 preceding siblings ...)
  2011-07-01  9:08 ` Jonathan Cameron
@ 2011-07-01 21:04 ` Arnd Bergmann
  14 siblings, 0 replies; 44+ messages in thread
From: Arnd Bergmann @ 2011-07-01 21:04 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

On Friday 01 July 2011 04:18:17 Nathan Royer wrote:
> +/* Structure for the following IOCTS's
> + * MPU_CONFIG_GYRO
> + * MPU_CONFIG_ACCEL
> + * MPU_CONFIG_COMPASS
> + * MPU_CONFIG_PRESSURE
> + * MPU_GET_CONFIG_GYRO
> + * MPU_GET_CONFIG_ACCEL
> + * MPU_GET_CONFIG_COMPASS
> + * MPU_GET_CONFIG_PRESSURE
> + *
> + * @key one of enum ext_slave_config_key
> + * @len length of data pointed to by data
> + * @apply zero if communication with the chip is not necessary, false otherwise
> + *        This flag can be used to select cached data or to refresh cashed data
> + *        cache data to be pushed later or push immediately.  If true and the
> + *        slave is on the secondary bus the MPU will first enger bypass mode
> + *        before calling the slaves .config or .get_config funcion
> + * @data pointer to the data to confgure or get
> + */
> +struct ext_slave_config {
> +       __u8 key;
> +       __u16 len;
> +       __u8 apply;
> +       void *data;
> +};

I'm a bit worried about the ioctl interface, it seems overloaded and has
problems with 32/64 bit compatibility. In general, having void pointers
in an structure that is used as an ioctl argument is a sign that the
interface is too complex. In fact, there are a number of reasons why you
should try to avoid pointers in there completely.

You should also try to avoid padding in structures. The definition you
have could be any of 64/96/128 bytes long depending on the architecture,
and leak kernel stack data.

Having separate commands on a single device file in order to do the
same operation on distinct pieces of hardware sounds like a wrong
abstraction of your devices. Instead, it would seem more appropriate
to represent each hardware endpoint (gyro/accel/compass/...) as one
character device, and let each of them export the same minimum set of
commands. However, the suggestion to use an existing subsystem (iio,
input, hwmon, ...) for each of the endpoints as appropriate would fit
better into the existing use cases.

I haven't been able to figure out if all those endpoints are just
I2C devices and could be handled by i2c drivers for those subsystems,
or if you have another hardware interface that groups of them. In the
latter case, would an MFD driver work for this? The idea of that is
essentially to prove a new bus_type without having to do all the
abstraction work when you already know all the devices behind it.


	Arnd

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01 16:39   ` Nathan Royer
@ 2011-07-03 11:29     ` Jonathan Cameron
  2011-07-04  8:16       ` Alan Cox
  2011-07-04 20:06       ` Eric Andersson
  0 siblings, 2 replies; 44+ messages in thread
From: Jonathan Cameron @ 2011-07-03 11:29 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Andrew Morton, Greg Kroah-Hartman, Jiri Kosina, Alan Cox,
	Jean Delvare, linux-kernel, linux-input, Chris Hudson,
	eric.andersson, eric.miao

On 07/01/11 17:39, Nathan Royer wrote:
>> Just out of interest, can you give details of what other patches are to
>> come?
>> I'm guessing more sensor drivers and I hate discovering I'm
>> implementing drivers
>> for devices that someone already has working code for!
> 
> I wanted to start off with only 1 driver for each type of slave and with
> only the MPU3050 and exclude the MPU6050 until we figure out exactly the
> best way to fit this into the kernel.

With such an extensive list of drivers, how about putting the whole current
lot in staging whilst we figure out the eventual form?  Probably not including
the ones we already have else where, but maybe we do want them as well, so we
can merge in new features?
> 
> The current full list of drivers we have developed in this architecture
> are as follows:
> MPU3050 and MPU6050 support in mldl_cfg.c
> # sesnors - accel
> accel/mpu6050.c  -
> accel/kxsd9.c
There is a driver for this in IIO but it exists more as an example of a trivial
driver than being fully fledged.  We've never had any interest before in doing
anything more complex with it.   I've cc'd Chris Hudson at Kionix, to comment on
what they have for these parts.
> accel/kxtf9.c
Over to Chris.

> accel/bma150.c
An input driver exists for that one. (cc'd Eric)

> accel/bma222.c
Not seen this one or the next that I can recall.
> accel/bma250.c

> accel/mma8450.c
> accel/mma845x.c
Err, naming clash ;)
Input driver has been posted... Cc'd another Eric.

> accel/lis331.c
Supported I think by the lis driver.

> accel/lsm303a.c
Not seen that one.

> accel/adxl34x.c
input driver.
> accel/lis3dh.c
also lis driver.
> # sensors - compass
> compass/ak8975.c
> compass/ak8972.c
> compass/ami30x.c
> compass/ami306.c
> compass/hmc5883.c
> compass/lsm303m.c
> compass/yas529.c
> compass/yas530.c
> compass/mmc314x.c
> compass/hscdtd002b.c
> compass/hscdtd004a.c
That is a whole host more compasses that I haven't seen drivers for. I think
we may have 2 of them in IIO...
> # sensors - pressure
> pressure/bma085.c
As Manuel Stahl confirmed he also has a driver for this.
> 


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-03 11:29     ` Jonathan Cameron
@ 2011-07-04  8:16       ` Alan Cox
  2011-07-06  1:49         ` Nathan Royer
  2011-07-04 20:06       ` Eric Andersson
  1 sibling, 1 reply; 44+ messages in thread
From: Alan Cox @ 2011-07-04  8:16 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Nathan Royer, Andrew Morton, Greg Kroah-Hartman, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input, Chris Hudson,
	eric.andersson, eric.miao

> > accel/bma150.c
> An input driver exists for that one. (cc'd Eric)

[Two input drivers - bma023 which I posted covers it too ;)]

> > # sensors - compass
> > compass/ak8975.c

We have a driver for this (I posted a while back)

> > compass/ak8972.c

> > compass/ami30x.c

AMI304 is identical to the AK8974 which we already have - Gram Hsieh
posted patches for this a while ago


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-03 11:29     ` Jonathan Cameron
  2011-07-04  8:16       ` Alan Cox
@ 2011-07-04 20:06       ` Eric Andersson
  1 sibling, 0 replies; 44+ messages in thread
From: Eric Andersson @ 2011-07-04 20:06 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Nathan Royer, Andrew Morton, Greg Kroah-Hartman, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input, Chris Hudson,
	eric.miao

> > # sensors - pressure
> > pressure/bma085.c
> As Manuel Stahl confirmed he also has a driver for this.

FYI - We are working on a new version of the bmp085 driver with support for
some new pressure sensors from Bosch Sensortec. The plan is to
contribute that code in a near future.

-- 
Best regards,
 Eric

http://www.unixphere.com

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-01  3:59 ` Chris Wolfe
@ 2011-07-05 18:08   ` Nathan Royer
  0 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-05 18:08 UTC (permalink / raw)
  To: Chris Wolfe
  Cc: Andrew Morton, Greg Kroah-Hartman, Jonathan Cameron, Jiri Kosina,
	Alan Cox, Jean Delvare, linux-kernel, linux-input

> Is this
> driver to the stage where someone can practically initialize the 3050
> and read low-level data without having the InvenSense MPL already
> installed? (and without basically doing I2C reads and writes from
> userspace)

Yes, this driver could be used to initialize the 3050 and do raw data reads
without the MPL library.  I haven't actually tried it, but you would set the
dmp_enable = 0, fifo_enable = 0 and on each IRQ do a read of
MPUREG_GYRO_XOUT_H..MPUREG_GYRO_ZOUT_L using the IOCTL MPU_READ.

To take advantage of the DMP, and the Master I2C bus you will need the MPL
library in user space.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-04  8:16       ` Alan Cox
@ 2011-07-06  1:49         ` Nathan Royer
  2011-07-06  9:07           ` Jonathan Cameron
  2011-07-06 10:54           ` Alan Cox
  0 siblings, 2 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-06  1:49 UTC (permalink / raw)
  To: Alan Cox, Jonathan Cameron
  Cc: Andrew Morton, Greg Kroah-Hartman, Jiri Kosina, Jean Delvare,
	linux-kernel, linux-input, Chris Hudson, eric.andersson,
	eric.miao

> > > accel/bma150.c
> > An input driver exists for that one. (cc'd Eric)
>
> [Two input drivers - bma023 which I posted covers it too ;)]
>
> > > # sensors - compass
> > > compass/ak8975.c
>
> We have a driver for this (I posted a while back)
>
> > > compass/ak8972.c
>
> > > compass/ami30x.c
>
> AMI304 is identical to the AK8974 which we already have - Gram Hsieh
> posted patches for this a while ago

It seems that some sensors are in input and but that most are in iio.
Obviously I don't want to dissent with both and put ours in misc, so how
do we make this better?  Should we work on cleaning this up.  If so should
we start moving the drivers that are in input to iio.

If this is the right thing to do, I could start working on merging the
current mpu3050 input interface and our misc interface to the iio
interface.  I'm still trying to wrap my head around the iio framework, but
I think one of the things I need is a uinput like interface for iio
(perhaps it exists, I just haven't figured it out yet).  If the DMP is
used, a buffer for the FIFO data would be created, and user space would be
responsible to push the sensor data back to iio after sensor fusion and
calibration.  Without the DMP, each sensor driver would act independently
providing their own raw data.

We still need a way to read and write registers and DMP memory during
runtime from user space.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-06  1:49         ` Nathan Royer
@ 2011-07-06  9:07           ` Jonathan Cameron
  2011-07-06 20:25             ` Nathan Royer
  2011-07-06 10:54           ` Alan Cox
  1 sibling, 1 reply; 44+ messages in thread
From: Jonathan Cameron @ 2011-07-06  9:07 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Alan Cox, Andrew Morton, Greg Kroah-Hartman, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input, Chris Hudson,
	eric.andersson, eric.miao, Hennerich, Michael, Manuel Stahl,
	linux-iio

On 07/06/11 02:49, Nathan Royer wrote:
>>>> accel/bma150.c
>>> An input driver exists for that one. (cc'd Eric)
>>
>> [Two input drivers - bma023 which I posted covers it too ;)]
>>
>>>> # sensors - compass
>>>> compass/ak8975.c
>>
>> We have a driver for this (I posted a while back)
>>
>>>> compass/ak8972.c
>>
>>>> compass/ami30x.c
>>
>> AMI304 is identical to the AK8974 which we already have - Gram Hsieh
>> posted patches for this a while ago
> 
> It seems that some sensors are in input and but that most are in iio.
> Obviously I don't want to dissent with both and put ours in misc, so how
> do we make this better? 
Definitely not misc - we don't want to end up with drivers in there purely
because they might fit in two other places!
> Should we work on cleaning this up.  If so should
> we start moving the drivers that are in input to iio.
Will cause all sorts of userspace issues.  a) iio is still in staging
and for good reason.  I really ought to update our todo list!
b) We don't have a transparent way to provide input interfaces for iio
devices - so that will mean mass abi breakge.  There have been some
discussions about how to do this (see Mark Brown's suggestions a month
or so ago), be it for very different reason.

Anyhow, I'm personally in favour of the current divide:
Basically if it's for human input (primarily) it goes in input (decision
on that is Dmitry's). Otherwise, IIO is happy to take drivers that don't
fit elsewhere (we deliberately have very wide scope).
> 
> If this is the right thing to do, I could start working on merging the
> current mpu3050 input interface and our misc interface to the iio
> interface.  I'm still trying to wrap my head around the iio framework, but
> I think one of the things I need is a uinput like interface for iio
> (perhaps it exists, I just haven't figured it out yet).
Nope. Not previously had a use case.
Unlike input we don't have a whole host of userspace code that is expecting to
receive data in a particular format so we don't have the standard use
case for uinput  (cc'd Manuel and Michael who have done a lot more on the userspace
end of IIO than I have + linux-iio for everyone else).  We do have a prototype
uinput based bridge to push event into input.  It is only intended for those rare
cases where someone really wants to use a 1000 dolar IMU as a mouse. Not relevant
here though.

> If the DMP is
err, DMP?
> used, a buffer for the FIFO data would be created, and user space would be
> responsible to push the sensor data back to iio after sensor fusion and
> calibration.  Without the DMP, each sensor driver would act independently
> providing their own raw data.

Strangely enough, your need to push data back is not dissimilar to what we have
discussed in the past for DACs. Right now we only have a slow and simple interface
for DACs, mainly because doing anything clever requires some fairly nasty additions
to the host bus drivers and no one has had the time.  Michael has almost
certainly thought more on this than I ever have!
> 
> We still need a way to read and write registers and DMP memory during
> runtime from user space.
I guess the DMP is the on device processor? So what you write varies a lot
with what firmware is loaded?

So let me just check I understand the data flow here.

Example

Magnetometer -> kernel -> userspace interface -> hideously complex algorithm ->
kernel -> mpu -> mpu fusion algorithm in relevant firmware -> userspace ?

So how do we know the mpu wants data?  Obvious options that might be the case
a) on chip fifo with flow control.
b) interrupt to request data?
c) Always feed latest value and it runs unsynchronised.

Do we actually have that hideously complex algorithm in userspace element
or did I invent that part?  If not, we might want to use some in kernel
hooks to pass the data back bypassing userspace entirely.

Jonathan 

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-06  1:49         ` Nathan Royer
  2011-07-06  9:07           ` Jonathan Cameron
@ 2011-07-06 10:54           ` Alan Cox
  2011-07-06 21:27             ` Nathan Royer
  1 sibling, 1 reply; 44+ messages in thread
From: Alan Cox @ 2011-07-06 10:54 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Jonathan Cameron, Andrew Morton, Greg Kroah-Hartman, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input, Chris Hudson,
	eric.andersson

> It seems that some sensors are in input and but that most are in iio.
> Obviously I don't want to dissent with both and put ours in misc, so
> how do we make this better?  Should we work on cleaning this up.  If
> so should we start moving the drivers that are in input to iio.

IIO provides a lot more flexibility and is rather newer, input provides
a more focussed interface. In some cases it may make sense to provide
different interfaces to each (eg atomspheric pressure doesn't fit well
into input, but 3 axis accelerometers fit perfectly)

> We still need a way to read and write registers and DMP memory during
> runtime from user space.

You probably want a driver for the MPU itself whih provides the
needed glue and also control interfaces (firmware load etc). That may
well be a drivers/misc item as I imagine that part is quite unique and
specialised.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-06  9:07           ` Jonathan Cameron
@ 2011-07-06 20:25             ` Nathan Royer
  0 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-06 20:25 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Alan Cox, Andrew Morton, Greg Kroah-Hartman, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input, Chris Hudson,
	eric.andersson, eric.miao, Hennerich, Michael, Manuel Stahl,
	linux-iio

> It is only intended for those rare
> cases where someone really wants to use a 1000 dolar IMU as a mouse.
> Not relevant here though.

We have a mouse application that is significantly less expensive than
$1000, so it might actually be relevant, in the future.

> > If the DMP is
> err, DMP?

Digital Motion Processor.  It is what we call the built in micro
controller on the mpu3050 that does sensor fusion.

> > used, a buffer for the FIFO data would be created, and user space
> would be
> > responsible to push the sensor data back to iio after sensor fusion
> and
> > calibration.  Without the DMP, each sensor driver would act
> independently
> > providing their own raw data.
>
> Strangely enough, your need to push data back is not dissimilar to what
> we have
> discussed in the past for DACs. Right now we only have a slow and
> simple interface
> for DACs, mainly because doing anything clever requires some fairly
> nasty additions
> to the host bus drivers and no one has had the time.  Michael has
> almost
> certainly thought more on this than I ever have!

A slow interface might be ok.  Highest traffic is usually in the low 10's
of bytes at 30hz.

> > We still need a way to read and write registers and DMP memory during
> > runtime from user space.
> I guess the DMP is the on device processor? So what you write varies a
> lot
> with what firmware is loaded?

Yes that is correct.

> So let me just check I understand the data flow here.
>
> Example
>
> Magnetometer -> kernel -> userspace interface -> hideously complex
> algorithm ->
> kernel -> mpu -> mpu fusion algorithm in relevant firmware -> userspace
> ?

Hideously complex algorithm or HCA for short :)

Your data path is more relevant for the mpu6050.  I'll explain what it
currently looks like on each chip 2 chips.  For the 3050 it looks like
this:
=======================
Accel -> mpu3050 -> DMP (6 Axis fusion) -> FIFO
         (gyro)                   ^
                                  |
                               Kernel
                                  |
FIFO -> Kernel -> user space -> 9 Axis fusion + HCA's-> application
                                  ^
                                  |
Compass -> kernel -> user space --/

For the mpu6050:
================
Compass -> mpu6050   -> DMP (9 Axis Fusion) -> Kernel -> User Space HCA's
-> Apps
          (gyro + accel)     ^                               |
                             |                               |
                             \---------------- Kernel <------/


As you might have seen we don't have an input or an iio interface for user
space to retrieve data.  Currently applications have to link in our MPL,
either standalone, or as a daemon, or something similar like the Android
sensor HAL.  What I'm trying to figure out is once data is ready to be
provided to applications, where should it come from, input, IIO, or a
daemon, before I start that part of development.  It seems like IIO was
built more for raw data, but that input has a facility to handle processed
data.

> So how do we know the mpu wants data?  Obvious options that might be
> the case
> a) on chip fifo with flow control.
> b) interrupt to request data?
> c) Always feed latest value and it runs unsynchronized.

The MPL (Motion Processing Library user space library that includes HCA's)
makes the decision when to feed data down, and does it in an
unsynchronized way.  It will do it shortly after processing a FIFO data
packet, but the timing does not need to be precise.

> Do we actually have that hideously complex algorithm in userspace
> element
> or did I invent that part?  If not, we might want to use some in kernel
> hooks to pass the data back bypassing userspace entirely.

Yes the HCA's exists.  They include 9 axis fusion, compass calibration
algorithms, and a number of other proprietary algorithms.  We've tried to
design the MPL so that they can be removed and still provide basic
functionality.  This basic functionality is available to be pushed into
the kernel if necessary, but we will still need a way to support the
HCA's.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-06 10:54           ` Alan Cox
@ 2011-07-06 21:27             ` Nathan Royer
  2011-07-07  7:40               ` Alan Cox
  0 siblings, 1 reply; 44+ messages in thread
From: Nathan Royer @ 2011-07-06 21:27 UTC (permalink / raw)
  To: Alan Cox
  Cc: Jonathan Cameron, Andrew Morton, Greg Kroah-Hartman, Jiri Kosina,
	Jean Delvare, linux-kernel, linux-input, Chris Hudson,
	eric.andersson

> > It seems that some sensors are in input and but that most are in iio.
> > Obviously I don't want to dissent with both and put ours in misc, so
> > how do we make this better?  Should we work on cleaning this up.  If
> > so should we start moving the drivers that are in input to iio.
>
> IIO provides a lot more flexibility and is rather newer, input provides
> a more focussed interface. In some cases it may make sense to provide
> different interfaces to each (eg atomspheric pressure doesn't fit well
> into input, but 3 axis accelerometers fit perfectly)
>
> > We still need a way to read and write registers and DMP memory during
> > runtime from user space.
>
> You probably want a driver for the MPU itself whih provides the
> needed glue and also control interfaces (firmware load etc). That may
> well be a drivers/misc item as I imagine that part is quite unique and
> specialised.

If I understand you correctly, you are suggesting that we create two
drivers for the mpu3050 part.  An MPU (Motion Processing Unit) driver and
a standalone Gyroscope driver.  The MPU if present would turn each sensor
(accel, compass, gyro, pressure) into an MPU slave, load and configure the
firmware, and provide a user space interface for customization and runtime
communication with the Hideously Complicated Algorithms (HCA's).

Each sensor driver would have two operating modes, standalone, and slave
to the MPU trying to re-use as much code as possible.  Current drivers
that have a standalone interface would need the slave interface added, and
those that we've written would need the stand alone interface added.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-06 21:27             ` Nathan Royer
@ 2011-07-07  7:40               ` Alan Cox
  2011-07-08  1:25                 ` Nathan Royer
  0 siblings, 1 reply; 44+ messages in thread
From: Alan Cox @ 2011-07-07  7:40 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Alan Cox, Jonathan Cameron, Andrew Morton, Greg Kroah-Hartman,
	Jiri Kosina, Jean Delvare, linux-kernel, linux-input,
	Chris Hudson, eric.andersson

> If I understand you correctly, you are suggesting that we create two
> drivers for the mpu3050 part.  An MPU (Motion Processing Unit) driver and
> a standalone Gyroscope driver.  The MPU if present would turn each sensor
> (accel, compass, gyro, pressure) into an MPU slave, load and configure the
> firmware, and provide a user space interface for customization and runtime
> communication with the Hideously Complicated Algorithms (HCA's).

Its hard to judge with what has been explained so far but the device
appears to be very different in the two operating modes so it might make
sense to have it as two drivers (or one driver with two very distinct
modes)

> Each sensor driver would have two operating modes, standalone, and slave
> to the MPU trying to re-use as much code as possible.  Current drivers
> that have a standalone interface would need the slave interface added, and
> those that we've written would need the stand alone interface added.

Can the slave interface appears to be an i²c bus too or is the interface
too different (ie could the smart mode create an i²c bus that it uses to
talk to the drivers to configure them for 'smart' mode)

I'm not entirely sure at this point I understand quite how all the pieces
fit together.

Alan

^ permalink raw reply	[flat|nested] 44+ messages in thread

* RE: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-07  7:40               ` Alan Cox
@ 2011-07-08  1:25                 ` Nathan Royer
  2011-07-08 11:21                   ` Jonathan Cameron
  0 siblings, 1 reply; 44+ messages in thread
From: Nathan Royer @ 2011-07-08  1:25 UTC (permalink / raw)
  To: Alan Cox
  Cc: Alan Cox, Jonathan Cameron, Andrew Morton, Greg Kroah-Hartman,
	Jiri Kosina, Jean Delvare, linux-kernel, linux-input,
	Chris Hudson, eric.andersson

> Its hard to judge with what has been explained so far but the device
> appears to be very different in the two operating modes so it might
> make
> sense to have it as two drivers (or one driver with two very distinct
> modes)

I'm thinking two distinct drivers.  One we should call the MPU controller
and the other the gyro driver.

Comparing it to the i2c architecture.  In the board file, each i2c bus is
initialized with information about each device on the bus.  An i2c_client
is created for each device and their probe functions called allowing the
driver to use the i2c_client to communicate with the device.

For the MPU controller, it would be similar registration, but the slaves
would be given to the MPU controller to control.  In the board file the
MPU controller would be initialized with which devices that it should
control.  The MPU controller would then probe each device for the
interface used to control it.  If that device is not probed then it should
act alone, otherwise it should allow the MPU controller to control it.

Regarding the interface, I'm still confused on how to resolve the
iio/input decision.  Should the devices use both?  I think the final
interface would be a combination of possibly all 3 interfaces, misc,
input, and iio.

Below is a discussion of the IOCTL's, how they are currently used, and how
I'm thinking about mapping them in between the 3 interfaces.

Our MPL needs the platform data, mostly the orientation of each device.
The relevant information can be made available as iio sysfs attributes for
each device. ========================================
#define MPU_GET_EXT_SLAVE_PLATFORM_DATA	\
	_IOWR(MPU_IOCTL, 0x02, struct ext_slave_platform_data)
#define MPU_GET_MPU_PLATFORM_DATA	\
	_IOWR(MPU_IOCTL, 0x02, struct mpu_platform_data)
#define MPU_GET_EXT_SLAVE_DESCR	\
	_IOWR(MPU_IOCTL, 0x02, struct ext_slave_descr)

These are simple register reads and register writes.  These can be
replaced with iio sysfs attributes for the gyro driver.
=======================================
#define MPU_READ		_IOWR(MPU_IOCTL, 0x10, struct
mpu_read_write)
#define MPU_WRITE		_IOW(MPU_IOCTL,  0x10, struct
mpu_read_write)

The following is used to interact at runtime with the DMP.  These will
need to remain as ioctls to drivers/misc/inv_mpu
========================================
#define MPU_READ_MEM		_IOWR(MPU_IOCTL, 0x11, struct
mpu_read_write)
#define MPU_WRITE_MEM		_IOW(MPU_IOCTL,  0x11, struct
mpu_read_write)
#define MPU_READ_FIFO		_IOWR(MPU_IOCTL, 0x12, struct
mpu_read_write)
#define MPU_WRITE_FIFO		_IOW(MPU_IOCTL,  0x12, struct
mpu_read_write)

These were used for reading raw data from each slave.  These can be
replaced by an appropriate IIO interface for raw data, implemented
separately by each slave driver.  Input can be then used for calibrated
data and can be fed back via uinput from user space.
========================================
#define MPU_READ_COMPASS	_IOR(MPU_IOCTL, 0x12, __u8)
#define MPU_READ_ACCEL		_IOR(MPU_IOCTL, 0x13, __u8)
#define MPU_READ_PRESSURE	_IOR(MPU_IOCTL, 0x14, __u8)

These are used to configure each slave.  These can be replaced by sysfs
attributes implemented by each slave driver in iio.
========================================================
#define MPU_CONFIG_GYRO		_IOW(MPU_IOCTL, 0x20, struct
ext_slave_config)
#define MPU_CONFIG_ACCEL	_IOW(MPU_IOCTL, 0x21, struct
ext_slave_config)
#define MPU_CONFIG_COMPASS	_IOW(MPU_IOCTL, 0x22, struct
ext_slave_config)
#define MPU_CONFIG_PRESSURE	_IOW(MPU_IOCTL, 0x23, struct
ext_slave_config)

#define MPU_GET_CONFIG_GYRO	_IOWR(MPU_IOCTL, 0x20, struct
ext_slave_config)
#define MPU_GET_CONFIG_ACCEL	_IOWR(MPU_IOCTL, 0x21, struct
ext_slave_config)
#define MPU_GET_CONFIG_COMPASS	_IOWR(MPU_IOCTL, 0x22, struct
ext_slave_config)
#define MPU_GET_CONFIG_PRESSURE	_IOWR(MPU_IOCTL, 0x23, struct
ext_slave_config)

This was used to start or stop the system.  This can be replaced with iio
sysfs power mode attribute for each slave, and an additional one for the
MPU controller.
=========================================
#define MPU_SUSPEND		_IOW(MPU_IOCTL, 0x30, __u32)
#define MPU_RESUME		_IOW(MPU_IOCTL, 0x31, __u32)

Selecting on /dev/mpu will return a MPU_PM_EVENT_SUSPEND_PREPARE or
MPU_PM_EVENT_POST_SUSPEND allowing user space to decide if it wants to
leave anything powered on when suspended.  This in conjunction with the
IGNORE_SYSTEM_SUSPEND allows the DMP plus gyro and/or accel to continue
running while the main processor is suspended.  This can be used to wake
up the main processor on a particular motion event, or to record motion
events to be reported when the processor later wakes up.  This ioctl
signals that user space has finished configuring all sensors and the MPU
controller and that the MPU controller can continue.
=======================================
#define MPU_PM_EVENT_HANDLED	_IO(MPU_IOCTL, 0x32)

Requested sensors is a common place to know which sensors are on, and to
specify which sensors to turn on the next time the system is started.
This can be removed since it is redundant with power mode attributes I'm
proposing be used by each slave and the MPU controller.
=======================================
#define MPU_GET_REQUESTED_SENSORS	_IOR(MPU_IOCTL, 0x40, __u8)
#define MPU_SET_REQUESTED_SENSORS	_IOW(MPU_IOCTL, 0x40, __u8)

See my note on MPU_PM_EVENT_HANDLED above.  This can be made a SysFs
attribute of drivers/misc/inv_mpu
=======================================
#define MPU_GET_IGNORE_SYSTEM_SUSPEND	_IOR(MPU_IOCTL, 0x41, __u8)
#define MPU_SET_IGNORE_SYSTEM_SUSPEND	_IOW(MPU_IOCTL, 0x41, __u8)

These are bitfield of how the MPU controller state.  Which devices are
currently suspended, which devices are running, if the i2c master is
bypassed, if the DMP is running.  This can be made a SysFs attribute, and
perhaps removed.
=======================================
#define MPU_GET_MLDL_STATUS		_IOR(MPU_IOCTL, 0x42, __u8)

This is a bitfield showing the status of which of the master i2c channels
have been enabled.  The mpu3050 has 1 channel, and the mpu6050 has 5
channels.  This can become a sysfs attribute
=======================================
#define MPU_GET_I2C_SLAVES_ENABLED	_IOR(MPU_IOCTL, 0x43, __u8)

After all is said and done this is files that would be made available:
/dev/mpu
	Iotcl interface for memory and fifo reads writes, read for events
include pm events and irq events.

/sys/bus/iio/devices/device[ACCEL]/power_state
/sys/bus/iio/devices/device[ACCEL]/accel_x_raw
/sys/bus/iio/devices/device[ACCEL]/accel_y_raw
/sys/bus/iio/devices/device[ACCEL]/accel_z_raw
/sys/bus/iio/devices/device[ACCEL]/<other attributes as appropriate>

/sys/bus/iio/devices/device[MAGN]/power_state
/sys/bus/iio/devices/device[MAGN]/magn_x_raw
/sys/bus/iio/devices/device[MAGN]/magn_y_raw
/sys/bus/iio/devices/device[MAGN]/magn_z_raw
/sys/bus/iio/devices/device[MAGN]/<other attributes as appropriate>

/sys/bus/iio/devices/device[GYRO]/power_state
/sys/bus/iio/devices/device[GYRO]/gyro_x_raw
/sys/bus/iio/devices/device[GYRO]/gyro_y_raw
/sys/bus/iio/devices/device[GYRO]/gyro_z_raw
/sys/bus/iio/devices/device[GYRO]/<other attributes as appropriate>

Then for input ABS_X, ABS_Y and ABS_Z for each of the sensors containing
calibrated data.  There are a number of other computed data values we
would want to make available, but these can also be made available in user
space by the MPL if necessary.

> Can the slave interface appears to be an i瞔 bus too or is the
> interface
> too different (ie could the smart mode create an i瞔 bus that it uses
> to
> talk to the drivers to configure them for 'smart' mode)

I'm not exactly sure of the details of what you are suggesting, but
somehow telling a slave that it is now being used in 'smart' mode is fine,
as long as the MPU controller can still read back the information it needs
to control it, for example setting up the mpu3050 i2c master.

> I'm not entirely sure at this point I understand quite how all the
> pieces
> fit together.

It is a fairly complicated system, and why I want to get a good idea of
what we want long term before working on further changes.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-08  1:25                 ` Nathan Royer
@ 2011-07-08 11:21                   ` Jonathan Cameron
  2011-07-08 16:24                     ` Nathan Royer
  0 siblings, 1 reply; 44+ messages in thread
From: Jonathan Cameron @ 2011-07-08 11:21 UTC (permalink / raw)
  To: Nathan Royer
  Cc: Alan Cox, Alan Cox, Andrew Morton, Greg Kroah-Hartman,
	Jiri Kosina, Jean Delvare, linux-kernel, linux-input,
	Chris Hudson, eric.andersson

On 07/08/11 02:25, Nathan Royer wrote:
>> Its hard to judge with what has been explained so far but the device
>> appears to be very different in the two operating modes so it might
>> make
>> sense to have it as two drivers (or one driver with two very distinct
>> modes)
> 
> I'm thinking two distinct drivers.  One we should call the MPU controller
> and the other the gyro driver.
> 
> Comparing it to the i2c architecture.  In the board file, each i2c bus is
> initialized with information about each device on the bus.  An i2c_client
> is created for each device and their probe functions called allowing the
> driver to use the i2c_client to communicate with the device.
> 
> For the MPU controller, it would be similar registration, but the slaves
> would be given to the MPU controller to control.  In the board file the
> MPU controller would be initialized with which devices that it should
> control.  The MPU controller would then probe each device for the
> interface used to control it.  If that device is not probed then it should
> act alone, otherwise it should allow the MPU controller to control it.
> 
> Regarding the interface, I'm still confused on how to resolve the
> iio/input decision.  Should the devices use both?  I think the final
> interface would be a combination of possibly all 3 interfaces, misc,
> input, and iio.
> 
> Below is a discussion of the IOCTL's, how they are currently used, and how
> I'm thinking about mapping them in between the 3 interfaces.
> 
> Our MPL needs the platform data, mostly the orientation of each device.
> The relevant information can be made available as iio sysfs attributes for
> each device. ========================================
> #define MPU_GET_EXT_SLAVE_PLATFORM_DATA	\
> 	_IOWR(MPU_IOCTL, 0x02, struct ext_slave_platform_data)
> #define MPU_GET_MPU_PLATFORM_DATA	\
> 	_IOWR(MPU_IOCTL, 0x02, struct mpu_platform_data)
> #define MPU_GET_EXT_SLAVE_DESCR	\
> 	_IOWR(MPU_IOCTL, 0x02, struct ext_slave_descr)
Perfectly acceptable to have sysfs attributes with input devices to, so that
doesn't matter so much.  Now there are issues with what they are.
Why is platform data passed via sysfs? It's platform data, so it belongs
in a board file or device tree.
> 
> These are simple register reads and register writes.  These can be
> replaced with iio sysfs attributes for the gyro driver.
> =======================================
> #define MPU_READ		_IOWR(MPU_IOCTL, 0x10, struct
> mpu_read_write)
> #define MPU_WRITE		_IOW(MPU_IOCTL,  0x10, struct
> mpu_read_write)
Hmm. These really don't belong in sysfs. They are opaque and completely
ungeneralizable.  If you have this of need to write general purpose stuff
then perhaps consider some of the work going on for communications between
asymmetric multiprocessor systems. 
> 
> The following is used to interact at runtime with the DMP.  These will
> need to remain as ioctls to drivers/misc/inv_mpu
> ========================================
> #define MPU_READ_MEM		_IOWR(MPU_IOCTL, 0x11, struct
> mpu_read_write)
> #define MPU_WRITE_MEM		_IOW(MPU_IOCTL,  0x11, struct
> mpu_read_write)
> #define MPU_READ_FIFO		_IOWR(MPU_IOCTL, 0x12, struct
> mpu_read_write)
> #define MPU_WRITE_FIFO		_IOW(MPU_IOCTL,  0x12, struct
> mpu_read_write)
These look fine.
> 
> These were used for reading raw data from each slave.  These can be
> replaced by an appropriate IIO interface for raw data, implemented
> separately by each slave driver.  Input can be then used for calibrated
> data and can be fed back via uinput from user space.
> ========================================
> #define MPU_READ_COMPASS	_IOR(MPU_IOCTL, 0x12, __u8)
> #define MPU_READ_ACCEL		_IOR(MPU_IOCTL, 0x13, __u8)
> #define MPU_READ_PRESSURE	_IOR(MPU_IOCTL, 0x14, __u8)
Those are fine.
> 
> These are used to configure each slave.  These can be replaced by sysfs
> attributes implemented by each slave driver in iio.
> ========================================================
> #define MPU_CONFIG_GYRO		_IOW(MPU_IOCTL, 0x20, struct
> ext_slave_config)
> #define MPU_CONFIG_ACCEL	_IOW(MPU_IOCTL, 0x21, struct
> ext_slave_config)
> #define MPU_CONFIG_COMPASS	_IOW(MPU_IOCTL, 0x22, struct
> ext_slave_config)
> #define MPU_CONFIG_PRESSURE	_IOW(MPU_IOCTL, 0x23, struct
> ext_slave_config)
> 
> #define MPU_GET_CONFIG_GYRO	_IOWR(MPU_IOCTL, 0x20, struct
> ext_slave_config)
> #define MPU_GET_CONFIG_ACCEL	_IOWR(MPU_IOCTL, 0x21, struct
> ext_slave_config)
> #define MPU_GET_CONFIG_COMPASS	_IOWR(MPU_IOCTL, 0x22, struct
> ext_slave_config)
> #define MPU_GET_CONFIG_PRESSURE	_IOWR(MPU_IOCTL, 0x23, struct
> ext_slave_config)
>
Again all fine.
 
> This was used to start or stop the system.  This can be replaced with iio
> sysfs power mode attribute for each slave, and an additional one for the
> MPU controller.
Ideally this wants to be handled via runtime pm so that the dependencies
are nicely handled.
> =========================================
> #define MPU_SUSPEND		_IOW(MPU_IOCTL, 0x30, __u32)
> #define MPU_RESUME		_IOW(MPU_IOCTL, 0x31, __u32)
> 
> Selecting on /dev/mpu will return a MPU_PM_EVENT_SUSPEND_PREPARE or
> MPU_PM_EVENT_POST_SUSPEND allowing user space to decide if it wants to
> leave anything powered on when suspended.  This in conjunction with the
> IGNORE_SYSTEM_SUSPEND allows the DMP plus gyro and/or accel to continue
> running while the main processor is suspended.  This can be used to wake
> up the main processor on a particular motion event, or to record motion
> events to be reported when the processor later wakes up.  This ioctl
> signals that user space has finished configuring all sensors and the MPU
> controller and that the MPU controller can continue.
> =======================================
> #define MPU_PM_EVENT_HANDLED	_IO(MPU_IOCTL, 0x32)
> 
> Requested sensors is a common place to know which sensors are on, and to
> specify which sensors to turn on the next time the system is started.
> This can be removed since it is redundant with power mode attributes I'm
> proposing be used by each slave and the MPU controller.
> =======================================
> #define MPU_GET_REQUESTED_SENSORS	_IOR(MPU_IOCTL, 0x40, __u8)
> #define MPU_SET_REQUESTED_SENSORS	_IOW(MPU_IOCTL, 0x40, __u8)
> 
> See my note on MPU_PM_EVENT_HANDLED above.  This can be made a SysFs
> attribute of drivers/misc/inv_mpu
> =======================================
> #define MPU_GET_IGNORE_SYSTEM_SUSPEND	_IOR(MPU_IOCTL, 0x41, __u8)
> #define MPU_SET_IGNORE_SYSTEM_SUSPEND	_IOW(MPU_IOCTL, 0x41, __u8)
Is this supported in runtime pm?  If not, perhaps it is a usecase that should
be.  Here we could disable the bus, but not for example relevant regulators.

> 
> These are bitfield of how the MPU controller state.  Which devices are
> currently suspended, which devices are running, if the i2c master is
> bypassed, if the DMP is running.  This can be made a SysFs attribute, and
> perhaps removed.
> =======================================
> #define MPU_GET_MLDL_STATUS		_IOR(MPU_IOCTL, 0x42, __u8)
If sysfs, it should be one attribute per device, not a bitfield.
> 
> This is a bitfield showing the status of which of the master i2c channels
> have been enabled.  The mpu3050 has 1 channel, and the mpu6050 has 5
> channels.  This can become a sysfs attribute
> =======================================
> #define MPU_GET_I2C_SLAVES_ENABLED	_IOR(MPU_IOCTL, 0x43, __u8)
I think this should be handled via some platform data / run time pm.

If there is something there and it's being talked to it should be enabled.
Otherwise not.
> 
> After all is said and done this is files that would be made available:
> /dev/mpu
> 	Iotcl interface for memory and fifo reads writes, read for events
> include pm events and irq events.
Make sure it is numbered. Perfectly reasonable to have more than one of these!
> 
> /sys/bus/iio/devices/device[ACCEL]/power_state
> /sys/bus/iio/devices/device[ACCEL]/accel_x_raw
> /sys/bus/iio/devices/device[ACCEL]/accel_y_raw
> /sys/bus/iio/devices/device[ACCEL]/accel_z_raw
> /sys/bus/iio/devices/device[ACCEL]/<other attributes as appropriate>
> 
> /sys/bus/iio/devices/device[MAGN]/power_state
> /sys/bus/iio/devices/device[MAGN]/magn_x_raw
> /sys/bus/iio/devices/device[MAGN]/magn_y_raw
> /sys/bus/iio/devices/device[MAGN]/magn_z_raw
> /sys/bus/iio/devices/device[MAGN]/<other attributes as appropriate>
> 
> /sys/bus/iio/devices/device[GYRO]/power_state
> /sys/bus/iio/devices/device[GYRO]/gyro_x_raw
> /sys/bus/iio/devices/device[GYRO]/gyro_y_raw
> /sys/bus/iio/devices/device[GYRO]/gyro_z_raw
> /sys/bus/iio/devices/device[GYRO]/<other attributes as appropriate>
> 
> Then for input ABS_X, ABS_Y and ABS_Z for each of the sensors containing
> calibrated data.  There are a number of other computed data values we
> would want to make available, but these can also be made available in user
> space by the MPL if necessary.
> 
>> Can the slave interface appears to be an i瞔 bus too or is the
>> interface
>> too different (ie could the smart mode create an i瞔 bus that it uses
>> to
>> talk to the drivers to configure them for 'smart' mode)
> 
> I'm not exactly sure of the details of what you are suggesting, but
> somehow telling a slave that it is now being used in 'smart' mode is fine,
> as long as the MPU controller can still read back the information it needs
> to control it, for example setting up the mpu3050 i2c master.
To my mind this should look a bit like the i2c bus multiplexers.  The only
difference is that under some conditions, devices won't respond (the master
has taken an exclusive lock on them).
> 
>> I'm not entirely sure at this point I understand quite how all the
>> pieces
>> fit together.
> 
> It is a fairly complicated system, and why I want to get a good idea of
> what we want long term before working on further changes.
Very sensible
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH 01/11] misc: inv_mpu primary header file and README file.
  2011-07-08 11:21                   ` Jonathan Cameron
@ 2011-07-08 16:24                     ` Nathan Royer
  0 siblings, 0 replies; 44+ messages in thread
From: Nathan Royer @ 2011-07-08 16:24 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Alan Cox, Alan Cox, Andrew Morton, Greg Kroah-Hartman,
	Jiri Kosina, Jean Delvare, linux-kernel, linux-input,
	Chris Hudson, eric.andersson

>> Our MPL needs the platform data, mostly the orientation of each device.
>> The relevant information can be made available as iio sysfs attributes for
>> each device. ========================================
>> #define MPU_GET_EXT_SLAVE_PLATFORM_DATA       \
>>       _IOWR(MPU_IOCTL, 0x02, struct ext_slave_platform_data)
>> #define MPU_GET_MPU_PLATFORM_DATA     \
>>       _IOWR(MPU_IOCTL, 0x02, struct mpu_platform_data)
>> #define MPU_GET_EXT_SLAVE_DESCR       \
>>       _IOWR(MPU_IOCTL, 0x02, struct ext_slave_descr)
> Perfectly acceptable to have sysfs attributes with input devices to, so that
> doesn't matter so much.  Now there are issues with what they are.
> Why is platform data passed via sysfs? It's platform data, so it belongs
> in a board file or device tree.

Yes I agree, but user space needs to read that information, my plan
was to make the information in the platform data available as read
only attributes.  Is there a better way?

>>
>> These are simple register reads and register writes.  These can be
>> replaced with iio sysfs attributes for the gyro driver.
>> =======================================
>> #define MPU_READ              _IOWR(MPU_IOCTL, 0x10, struct
>> mpu_read_write)
>> #define MPU_WRITE             _IOW(MPU_IOCTL,  0x10, struct
>> mpu_read_write)
> Hmm. These really don't belong in sysfs. They are opaque and completely
> ungeneralizable.  If you have this of need to write general purpose stuff
> then perhaps consider some of the work going on for communications between
> asymmetric multiprocessor systems.

I think this particular interface is redundant with MPU_CFG_GYRO.
I'll need to look through our user space code, but I think 1 attribute
per register will remove the need for these general purpose i2c read
and write.

>> This was used to start or stop the system.  This can be replaced with iio
>> sysfs power mode attribute for each slave, and an additional one for the
>> MPU controller.
> Ideally this wants to be handled via runtime pm so that the dependencies
> are nicely handled.

Ok, I'll look into runtime pm.

>> =========================================
>> #define MPU_SUSPEND           _IOW(MPU_IOCTL, 0x30, __u32)
>> #define MPU_RESUME            _IOW(MPU_IOCTL, 0x31, __u32)
>>
>> Selecting on /dev/mpu will return a MPU_PM_EVENT_SUSPEND_PREPARE or
>> MPU_PM_EVENT_POST_SUSPEND allowing user space to decide if it wants to
>> leave anything powered on when suspended.  This in conjunction with the
>> IGNORE_SYSTEM_SUSPEND allows the DMP plus gyro and/or accel to continue
>> running while the main processor is suspended.  This can be used to wake
>> up the main processor on a particular motion event, or to record motion
>> events to be reported when the processor later wakes up.  This ioctl
>> signals that user space has finished configuring all sensors and the MPU
>> controller and that the MPU controller can continue.
>> =======================================
>> #define MPU_PM_EVENT_HANDLED  _IO(MPU_IOCTL, 0x32)
>>
>> Requested sensors is a common place to know which sensors are on, and to
>> specify which sensors to turn on the next time the system is started.
>> This can be removed since it is redundant with power mode attributes I'm
>> proposing be used by each slave and the MPU controller.
>> =======================================
>> #define MPU_GET_REQUESTED_SENSORS     _IOR(MPU_IOCTL, 0x40, __u8)
>> #define MPU_SET_REQUESTED_SENSORS     _IOW(MPU_IOCTL, 0x40, __u8)
>>
>> See my note on MPU_PM_EVENT_HANDLED above.  This can be made a SysFs
>> attribute of drivers/misc/inv_mpu
>> =======================================
>> #define MPU_GET_IGNORE_SYSTEM_SUSPEND _IOR(MPU_IOCTL, 0x41, __u8)
>> #define MPU_SET_IGNORE_SYSTEM_SUSPEND _IOW(MPU_IOCTL, 0x41, __u8)
> Is this supported in runtime pm?  If not, perhaps it is a usecase that should
> be.  Here we could disable the bus, but not for example relevant regulators.

User space needs a way to control if the chip is left powered or not.
In our use case we will load different firmware that keeps event
records in DMP memory, or generate an IRQ on event.  If nobody is user
land wants this feature enabled, then we completely sleep the chip.
I'll look to see if this is supported by runtime pm, not too familiar
with runtime pm... yet.

>> These are bitfield of how the MPU controller state.  Which devices are
>> currently suspended, which devices are running, if the i2c master is
>> bypassed, if the DMP is running.  This can be made a SysFs attribute, and
>> perhaps removed.
>> =======================================
>> #define MPU_GET_MLDL_STATUS           _IOR(MPU_IOCTL, 0x42, __u8)
> If sysfs, it should be one attribute per device, not a bitfield.

Ok, agreed.

>>
>> This is a bitfield showing the status of which of the master i2c channels
>> have been enabled.  The mpu3050 has 1 channel, and the mpu6050 has 5
>> channels.  This can become a sysfs attribute
>> =======================================
>> #define MPU_GET_I2C_SLAVES_ENABLED    _IOR(MPU_IOCTL, 0x43, __u8)
> I think this should be handled via some platform data / run time pm.
>
> If there is something there and it's being talked to it should be enabled.
> Otherwise not.

Correct,  this was actually never necessary.  I will just remove it.

>>
>> After all is said and done this is files that would be made available:
>> /dev/mpu
>>       Iotcl interface for memory and fifo reads writes, read for events
>> include pm events and irq events.
> Make sure it is numbered. Perfectly reasonable to have more than one of these!

If so I could get rid of the ioctl interface and replace it with
/dev/mpumem read() and write() and a /dev/mpufifo read() and write()
and /dev/mpupm read() for pm events if there isn't a better API for
runtime pm.  Which do you think would be better, several /dev devices
with read/write or a single /dev file with ioctl's?

>>
>> I'm not exactly sure of the details of what you are suggesting, but
>> somehow telling a slave that it is now being used in 'smart' mode is fine,
>> as long as the MPU controller can still read back the information it needs
>> to control it, for example setting up the mpu3050 i2c master.
> To my mind this should look a bit like the i2c bus multiplexers.  The only
> difference is that under some conditions, devices won't respond (the master
> has taken an exclusive lock on them).

I like that idea.  That means that certain interactions would return
-EBUSY if locked by the master.

^ permalink raw reply	[flat|nested] 44+ messages in thread

end of thread, other threads:[~2011-07-08 16:24 UTC | newest]

Thread overview: 44+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-07-01  2:18 [PATCH 01/11] misc: inv_mpu primary header file and README file Nathan Royer
2011-07-01  2:18 ` [PATCH 02/11] misc: mpu3050 Register definition and Private data Nathan Royer
2011-07-01  2:18 ` [PATCH 03/11] misc: mpu3050 /dev/mpu implementation Nathan Royer
2011-07-01  2:18 ` [PATCH 04/11] misc: IRQ handling for MPU3050 and slave devices Nathan Royer
2011-07-01  2:18 ` [PATCH 05/11] misc: MPU3050 and slave device configuration Nathan Royer
2011-07-01 17:55   ` Nathan Royer
2011-07-01  2:18 ` [PATCH 06/11] misc: inv_mpu logging and debugging support Nathan Royer
2011-07-01  2:18 ` [PATCH 07/11] misc: I2C communication with the MPU3050 and slave devices Nathan Royer
2011-07-01  2:18 ` [PATCH 08/11] misc: Kconfig and Makefile changes for inv_mpu driver Nathan Royer
2011-07-01 17:10   ` Randy Dunlap
2011-07-01  2:18 ` [PATCH 09/11] misc: Add slave driver for kxtf9 accelerometer Nathan Royer
2011-07-01  2:18 ` [PATCH 10/11] misc: Add slave driver for ak8975 compass driver Nathan Royer
2011-07-01  2:18 ` [PATCH 11/11] misc: Add slave driver for bma085 pressure sensor Nathan Royer
2011-07-01  7:56   ` Alan Cox
2011-07-01  8:47     ` Jean Delvare
2011-07-01 14:28     ` Chris Wolfe
2011-07-01 14:41       ` Alan Cox
2011-07-01 15:52         ` Chris Wolfe
2011-07-01 17:00           ` Alan Cox
2011-07-01 17:56             ` Nathan Royer
2011-07-01 16:09         ` Jean Delvare
2011-07-01  9:05   ` Jonathan Cameron
2011-07-01 10:35     ` Manuel Stahl
2011-07-01  3:09 ` [PATCH 01/11] misc: inv_mpu primary header file and README file Greg KH
2011-07-01  7:29   ` Alan Cox
2011-07-01  9:00   ` Jonathan Cameron
2011-07-01  3:59 ` Chris Wolfe
2011-07-05 18:08   ` Nathan Royer
2011-07-01  7:53 ` Alan Cox
2011-07-01  9:08 ` Jonathan Cameron
2011-07-01 16:39   ` Nathan Royer
2011-07-03 11:29     ` Jonathan Cameron
2011-07-04  8:16       ` Alan Cox
2011-07-06  1:49         ` Nathan Royer
2011-07-06  9:07           ` Jonathan Cameron
2011-07-06 20:25             ` Nathan Royer
2011-07-06 10:54           ` Alan Cox
2011-07-06 21:27             ` Nathan Royer
2011-07-07  7:40               ` Alan Cox
2011-07-08  1:25                 ` Nathan Royer
2011-07-08 11:21                   ` Jonathan Cameron
2011-07-08 16:24                     ` Nathan Royer
2011-07-04 20:06       ` Eric Andersson
2011-07-01 21:04 ` Arnd Bergmann

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).