summaryrefslogtreecommitdiff
path: root/drivers/iio/magnetometer
diff options
context:
space:
mode:
authorXiaohui Tao <xtao@nvidia.com>2014-01-08 14:31:34 -0800
committerMitch Luban <mluban@nvidia.com>2014-01-09 14:20:20 -0800
commitc401827da87830d26426a1e05fd2a4fc7523fef5 (patch)
treef6fd4e58d3abd86f819de490f29cbf8204f9d9ae /drivers/iio/magnetometer
parent168a30189073179c1394e2ec8b19de8c6341cd20 (diff)
Invensense V5.2.0 code drop for K310/KK
Change-Id: I166b0dc65bb90edd582bb0121a59683a9ce2a4af Signed-off-by: Xiaohui Tao <xtao@nvidia.com> Reviewed-on: http://git-master/r/353453 GVS: Gerrit_Virtual_Submit Reviewed-by: Mitch Luban <mluban@nvidia.com>
Diffstat (limited to 'drivers/iio/magnetometer')
-rw-r--r--drivers/iio/magnetometer/inv_compass/Kconfig36
-rw-r--r--drivers/iio/magnetometer/inv_compass/Makefile36
-rw-r--r--drivers/iio/magnetometer/inv_compass/README176
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ak89xx_core.c590
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ak89xx_iio.h144
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ak89xx_ring.c138
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ak89xx_trigger.c75
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ami306_core.c570
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ami306_iio.h159
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ami306_ring.c163
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_ami306_trigger.c90
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c969
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_yas53x_iio.h172
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_yas53x_ring.c165
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_yas53x_trigger.c91
15 files changed, 3574 insertions, 0 deletions
diff --git a/drivers/iio/magnetometer/inv_compass/Kconfig b/drivers/iio/magnetometer/inv_compass/Kconfig
new file mode 100644
index 000000000000..b5c8c44fd7ed
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/Kconfig
@@ -0,0 +1,36 @@
+#
+# Kconfig for Invensense IIO compass drivers of 3rd party compass devices.
+#
+
+# Yamaha YAS530/YAS532/YAS533
+config INV_YAS53X_IIO
+ tristate "Invensense IIO driver for Yamaha YAS530/YAS532/YAS533 compass"
+ depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF
+ default n
+ help
+ This driver supports the Yamaha YAS530/YAS532/YAS533. It is the Invensense
+ implementation of YAS53x series compass devices.
+ This driver can be built as a module. The module will be called
+ inv_yas53x_iio.
+
+# Aichi AMI306
+config INV_AMI306_IIO
+ tristate "Invensense IIO driver for Aichi AMI306 compass"
+ depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF
+ default n
+ help
+ This driver supports the Aichi AMI306 compass. It is the Invensense
+ IIO implementation for the AMI306 compass device.
+ This driver can be built as a module. The module will be called
+ inv-ami306-iio.
+
+# Asahi Kasei AK8975/AK8972/AK8963
+config INV_AK89XX_IIO
+ tristate "Invensense IIO driver for Asahi Kasei AK8975/AK8972/AK8963 compass"
+ depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF
+ default n
+ help
+ This driver supports the Asahi Kasei AK8975/AK8972/AK8963 compasses. It is the Invensense
+ IIO implementation of AK89xx series compass devices.
+ This driver can be built as a module. The module will be called
+ inv-ak89xx-iio. \ No newline at end of file
diff --git a/drivers/iio/magnetometer/inv_compass/Makefile b/drivers/iio/magnetometer/inv_compass/Makefile
new file mode 100644
index 000000000000..47c4271d3744
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/Makefile
@@ -0,0 +1,36 @@
+#
+# Makefile for Invensense IIO compass drivers of 3rd party compass devices.
+#
+
+# Yamaha YAS530/YAS532/YAS533
+obj-$(CONFIG_INV_YAS53X_IIO) += inv_yas53x.o
+
+inv_yas53x-objs := inv_yas53x_core.o
+inv_yas53x-objs += inv_yas53x_ring.o
+inv_yas53x-objs += inv_yas53x_trigger.o
+
+CFLAGS_inv_yas53x_core.o += -Idrivers/staging/iio
+CFLAGS_inv_yas53x_ring.o += -Idrivers/staging/iio
+CFLAGS_inv_yas53x_trigger.o += -Idrivers/staging/iio
+
+# Aichi AMI306
+obj-$(CONFIG_INV_AMI306_IIO) += inv-ami306-iio.o
+
+inv-ami306-iio-objs := inv_ami306_core.o
+inv-ami306-iio-objs += inv_ami306_ring.o
+inv-ami306-iio-objs += inv_ami306_trigger.o
+
+CFLAGS_inv_ami306_core.o += -Idrivers/staging/iio
+CFLAGS_inv_ami306_ring.o += -Idrivers/staging/iio
+CFLAGS_inv_ami306_trigger.o += -Idrivers/staging/iio
+
+# Asahi Kasei AK8975/AK8972/AK8963
+obj-$(CONFIG_INV_AK89XX_IIO) += inv-ak89xx-iio.o
+
+inv-ak89xx-iio-objs := inv_ak89xx_core.o
+inv-ak89xx-iio-objs += inv_ak89xx_ring.o
+inv-ak89xx-iio-objs += inv_ak89xx_trigger.o
+
+CFLAGS_inv_ak89xx_core.o += -Idrivers/staging/iio
+CFLAGS_inv_ak89xx_ring.o += -Idrivers/staging/iio
+CFLAGS_inv_ak89xx_trigger.o += -Idrivers/staging/iio \ No newline at end of file
diff --git a/drivers/iio/magnetometer/inv_compass/README b/drivers/iio/magnetometer/inv_compass/README
new file mode 100644
index 000000000000..54f2bb8ded2e
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/README
@@ -0,0 +1,176 @@
+Kernel driver
+Author: Invensense <http://invensense.com>
+
+Table of Contents:
+==================
+- Description
+- Integrating the Driver in the Linux Kernel
+- Board and Platform Data
+ > Platform Data
+- Board File Modifications for compass
+ > AMI306
+ > YAS530/532/533
+- IIO Subsystem
+ > Communicating with the Driver in Userspace
+- Streaming Data to an Userspace Application
+- Test Applications
+ > Running Test Applications with AMI306 or YAS53x
+
+Description
+===========
+This document describes how to install the Invensense device driver for AMI306
+and YAS53x series compass chip into a Linux kernel. The Invensense driver
+currently supports the following sensors:
+- AMI306
+- YAS530
+- YAS532
+- YAS533
+
+Please refer to the appropriate product specification
+document for further information regarding the slave address.
+
+The following files are included in this package:
+- Kconfig
+- Makefile
+- inv_ami306_core.c
+- inv_ami306_ring.c
+- inv_ami306_trigger.c
+- inv_ami306_iio.h
+- inv_yas53x_core.c
+- inv_yas53x_ring.c
+- inv_yas53x_trigger.c
+- inv_yas53x_iio.h
+
+Integrating the Driver in the Linux Kernel
+==========================================
+Please add the files as follows:
+- Add all above files to drivers/staging/iio/magnetometer/inv_compass
+(another directory is acceptable, but this is the recommended destination)
+
+In order to see the driver in menuconfig when building the kernel, please
+make modifications as shown below:
+
+ modify "drivers/staging/iio/magnetometer/Kconfig" with:
+ >> source "drivers/staging/iio/magnetometer/inv_compass/Kconfig"
+
+ modify "drivers/staging/iio/magnetometer/Makefile" with:
+ >> obj-y += inv_compass/
+
+
+Board and Platform Data
+=======================
+In order to recognize the Invensense device on the I2C bus, the board file must
+be modified.
+The i2c_board_info instance must be defined as shown below.
+
+Platform Data
+-------------
+The platform data (orientation matrix and secondary bus configurations) must be
+modified as show below, according to your particular platform configuration.
+
+Board File Modifications for Secondary I2C Configuration
+========================================================
+For the Panda Board, the board file can be found at
+arch/arm/mach-omap2/board-omap4panda.c.
+Please modify the pertinent baord file in your system according to the examples
+shown below:
+
+AMI306
+-------------------------------------------------
+static struct mpu_platform_data compass_data = {
+ .orientation = { 0, 0, 1,
+ 0, 1, 0,
+ 1, 0, 0 },
+};
+
+static struct i2c_board_info __initdata chip_board_info[] = {
+ {
+ I2C_BOARD_INFO("ami306", 0x0E),
+ .platform_data = &compass_data,
+ },
+};
+
+YAS53x(Use YAS532 as an example)
+-------------------------------------------------
+static struct mpu_platform_data compass_data = {
+ .orientation = { 0, -1, 0,
+ 1, 0, 0,
+ 0, 0, 1 },
+};
+
+static struct i2c_board_info __initdata compass_board_info[] = {
+ {
+ I2C_BOARD_INFO("yas532", 0x2E),
+ .platform_data = &compass_data,
+ },
+};
+
+IIO subsystem
+=============
+A successful installation will create the following two new directories under
+/sys/bus/iio/devices:
+ - iio:device0
+ - trigger0
+
+Also, a new file, "iio:device0", will be created in the /dev/ diretory.
+(if you have more than one IIO device, the file will be named "iio:deviceX",
+where X is a number)
+
+
+Communicating with the Driver in Userspace
+------------------------------------------
+The driver generates several files in sysfs upon installation.
+These files are used to communicate with the driver. The files can be found
+at /sys/bus/iio/devices/iio:device0 (or ../iio:deviceX as shown above).
+
+A brief description of the pertinent files for each Invensense device is shown
+below:
+
+AMI306
+--------
+compass_matrix (read-only)
+--show the orientation matrix obtained from the board file.
+
+sampling_frequency(read and write)
+--show and change the sampling rate of the sensor.
+
+YAS53x
+---------------------
+YAS53x has all the attributes AMI306 has. It has one more additional attribute:
+
+overunderflow(read-only)
+--value 1 shows an overflow or underflow happens. Need to write into it to make
+ it zero.
+
+Streaming Data to an Userspace Application
+==========================================
+When streaming data to an userspace application, we recommend that you access
+compass data via /dev/iio:device0.
+
+Please follow the steps below to read data at a constant rate from the driver:
+
+1. Write the desired output rate to sampling_frequency.
+2. Write 1 to enable to turn on the event.
+3. Read /dev/iio:device0 to get a string of gyro/accel/compass data.
+4. Parse this string to obtain each compass element.
+
+Test Applications
+=================
+A test application is located under software/simple_apps/mpu_iio.
+This application is stand-alone in that it cannot be run concurrently with other
+entities trying to access the device node(s) or sysfs entries; in particular,
+the
+
+Running Test Applications
+---------------------------------------------------------
+To run test applications with AMI306 or YAS53x devices,
+please use the following commands:
+
+1. for ami306:
+ mpu_iio -n ami306 -c 10 -l 3
+
+2. for yas532:
+ mpu_iio -n yas532 -c 10 -l 3
+
+Please use mpu_iio.c and iio_utils.h as example code for your development
+purposes.
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ak89xx_core.c b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_core.c
new file mode 100644
index 000000000000..a781fd398225
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_core.c
@@ -0,0 +1,590 @@
+/*
+* Copyright (C) 2013 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*/
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "inv_ak89xx_iio.h"
+#include "sysfs.h"
+#include "inv_test/inv_counters.h"
+
+static s64 get_time_ns(void)
+{
+ struct timespec ts;
+ ktime_get_ts(&ts);
+ return timespec_to_ns(&ts);
+}
+
+/**
+ * inv_serial_read() - Read one or more bytes from the device registers.
+ * @st: Device driver instance.
+ * @reg: First device register to be read from.
+ * @length: Number of bytes to read.
+ * @data: Data read from device.
+ * NOTE: The slave register will not increment when reading from the FIFO.
+ */
+int inv_serial_read(struct inv_ak89xx_state_s *st, u8 reg, u16 length, u8 *data)
+{
+ int result;
+ INV_I2C_INC_COMPASSWRITE(3);
+ INV_I2C_INC_COMPASSREAD(length);
+ result = i2c_smbus_read_i2c_block_data(st->i2c, reg, length, data);
+ if (result != length) {
+ if (result < 0)
+ return result;
+ else
+ return -EINVAL;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * inv_serial_single_write() - Write a byte to a device register.
+ * @st: Device driver instance.
+ * @reg: Device register to be written to.
+ * @data: Byte to write to device.
+ */
+int inv_serial_single_write(struct inv_ak89xx_state_s *st, u8 reg, u8 data)
+{
+ u8 d[1];
+ d[0] = data;
+ INV_I2C_INC_COMPASSWRITE(3);
+
+ return i2c_smbus_write_i2c_block_data(st->i2c, reg, 1, d);
+}
+
+static int ak89xx_init(struct inv_ak89xx_state_s *st)
+{
+ int result = 0;
+ unsigned char serial_data[3];
+
+ result = inv_serial_single_write(st, AK89XX_REG_CNTL,
+ AK89XX_CNTL_MODE_POWER_DOWN);
+ if (result) {
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ return result;
+ }
+ /* Wait at least 100us */
+ udelay(100);
+
+ result = inv_serial_single_write(st, AK89XX_REG_CNTL,
+ AK89XX_CNTL_MODE_FUSE_ACCESS);
+ if (result) {
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ return result;
+ }
+
+ /* Wait at least 200us */
+ udelay(200);
+
+ result = inv_serial_read(st, AK89XX_FUSE_ASAX, 3, serial_data);
+ if (result) {
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ return result;
+ }
+
+ st->asa[0] = serial_data[0];
+ st->asa[1] = serial_data[1];
+ st->asa[2] = serial_data[2];
+
+ result = inv_serial_single_write(st, AK89XX_REG_CNTL,
+ AK89XX_CNTL_MODE_POWER_DOWN);
+ if (result) {
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ return result;
+ }
+ udelay(100);
+
+ return result;
+}
+
+int ak89xx_read(struct inv_ak89xx_state_s *st, short rawfixed[3])
+{
+ unsigned char regs[8];
+ unsigned char *stat = &regs[0];
+ unsigned char *stat2 = &regs[7];
+ int result = 0;
+ int status = 0;
+
+ result = inv_serial_read(st, AK89XX_REG_ST1, 8, regs);
+ if (result) {
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ return result;
+ }
+
+ rawfixed[0] = (short)((regs[2]<<8) | regs[1]);
+ rawfixed[1] = (short)((regs[4]<<8) | regs[3]);
+ rawfixed[2] = (short)((regs[6]<<8) | regs[5]);
+
+ /*
+ * ST : data ready -
+ * Measurement has been completed and data is ready to be read.
+ */
+ if (*stat & 0x01)
+ status = 0;
+
+ /*
+ * 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 = 0x04;
+ /*
+ * 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 = 0x08;
+ /*
+ * 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 = 0;
+ }
+
+ /*
+ * 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 (1) {
+ unsigned char scale = 0;
+ if (st->compass_id == COMPASS_ID_AK8963)
+ scale = st->compass_scale;
+ result = inv_serial_single_write(st, AK89XX_REG_CNTL,
+ (scale << 4) | AK89XX_CNTL_MODE_SNG_MEASURE);
+ if (result) {
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ return result;
+ }
+ } else
+ pr_err("%s, no next measure(0x%x,0x%x)\n", __func__,
+ *stat, *stat2);
+
+ if (status)
+ pr_err("%s, line=%d, status=%d\n", __func__, __LINE__, status);
+
+ return status;
+}
+
+/**
+ * ak89xx_read_raw() - read raw method.
+ */
+static int ak89xx_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask) {
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ int scale = 0;
+
+ switch (mask) {
+ case 0:
+ if (!(iio_buffer_enabled(indio_dev)))
+ return -EINVAL;
+ if (chan->type == IIO_MAGN) {
+ *val = st->compass_data[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_MAGN) {
+ if (st->compass_id == COMPASS_ID_AK8975)
+ scale = 9830;
+ else if (st->compass_id == COMPASS_ID_AK8972)
+ scale = 19661;
+ else if (st->compass_id == COMPASS_ID_AK8963) {
+ if (st->compass_scale)
+ scale = 4915; /* 16 bit */
+ else
+ scale = 19661; /* 14 bit */
+ }
+ scale *= (1L << 15);
+ *val = scale;
+ return IIO_VAL_INT;
+ }
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t ak89xx_value_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ short c[3];
+
+ mutex_lock(&indio_dev->mlock);
+ c[0] = st->compass_data[0];
+ c[1] = st->compass_data[1];
+ c[2] = st->compass_data[2];
+ mutex_unlock(&indio_dev->mlock);
+ return sprintf(buf, "%d, %d, %d\n", c[0], c[1], c[2]);
+}
+
+static ssize_t ak89xx_scale_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ int scale = 0;
+
+ if (st->compass_id == COMPASS_ID_AK8975)
+ scale = 9830;
+ else if (st->compass_id == COMPASS_ID_AK8972)
+ scale = 19661;
+ else if (st->compass_id == COMPASS_ID_AK8963) {
+ if (st->compass_scale)
+ scale = 4915; /* 16 bit */
+ else
+ scale = 19661; /* 14 bit */
+ }
+ scale *= (1L << 15);
+ return sprintf(buf, "%d\n", scale);
+}
+
+static ssize_t ak89xx_rate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ /* transform delay in ms to rate */
+ return sprintf(buf, "%d\n", (1000 / st->delay));
+}
+
+/**
+ * ak89xx_matrix_show() - show orientation matrix
+ */
+static ssize_t ak89xx_matrix_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ signed char *m;
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ m = st->plat_data.orientation;
+ return sprintf(buf,
+ "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+}
+
+void set_ak89xx_enable(struct iio_dev *indio_dev, bool enable)
+{
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ int result = 0;
+ unsigned char scale = 0;
+
+ if (st->compass_id == COMPASS_ID_AK8963)
+ scale = st->compass_scale;
+
+ if (enable) {
+ result = inv_serial_single_write(st, AK89XX_REG_CNTL,
+ (scale << 4) | AK89XX_CNTL_MODE_SNG_MEASURE);
+ if (result)
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ schedule_delayed_work(&st->work,
+ msecs_to_jiffies(st->delay));
+ } else {
+ cancel_delayed_work_sync(&st->work);
+ result = inv_serial_single_write(st, AK89XX_REG_CNTL,
+ (scale << 4) | AK89XX_CNTL_MODE_POWER_DOWN);
+ if (result)
+ pr_err("%s, line=%d\n", __func__, __LINE__);
+ mdelay(1); /* wait at least 100us */
+ }
+}
+
+static ssize_t ak89xx_scale_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ unsigned long data, result;
+
+ result = kstrtoul(buf, 10, &data);
+ if (result)
+ return result;
+ if (st->compass_id == COMPASS_ID_AK8963)
+ st->compass_scale = !!data;
+ return count;
+}
+
+static ssize_t ak89xx_rate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ int error;
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+
+ error = kstrtoul(buf, 10, &data);
+ if (error)
+ return error;
+ /* transform rate to delay in ms */
+ data = 1000 / data;
+ if (data > AK89XX_MAX_DELAY)
+ data = AK89XX_MAX_DELAY;
+ if (data < AK89XX_MIN_DELAY)
+ data = AK89XX_MIN_DELAY;
+ st->delay = (unsigned int) data;
+ return count;
+}
+
+static void ak89xx_work_func(struct work_struct *work)
+{
+ struct inv_ak89xx_state_s *st =
+ container_of((struct delayed_work *)work,
+ struct inv_ak89xx_state_s, work);
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ unsigned long delay = msecs_to_jiffies(st->delay);
+
+ mutex_lock(&indio_dev->mlock);
+ if (!(iio_buffer_enabled(indio_dev)))
+ goto error_ret;
+
+ st->timestamp = get_time_ns();
+ schedule_delayed_work(&st->work, delay);
+ inv_read_ak89xx_fifo(indio_dev);
+ INV_I2C_INC_COMPASSIRQ();
+
+error_ret:
+ mutex_unlock(&indio_dev->mlock);
+}
+
+static const struct iio_chan_spec compass_channels[] = {
+ {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_AK89XX_SCAN_MAGN_X,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Y,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_AK89XX_SCAN_MAGN_Y,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Z,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_AK89XX_SCAN_MAGN_Z,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(INV_AK89XX_SCAN_TIMESTAMP)
+};
+
+static DEVICE_ATTR(value, S_IRUGO, ak89xx_value_show, NULL);
+static DEVICE_ATTR(scale, S_IRUGO | S_IWUSR, ak89xx_scale_show,
+ ak89xx_scale_store);
+static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, ak89xx_rate_show,
+ ak89xx_rate_store);
+static DEVICE_ATTR(compass_matrix, S_IRUGO, ak89xx_matrix_show, NULL);
+
+static struct attribute *inv_ak89xx_attributes[] = {
+ &dev_attr_value.attr,
+ &dev_attr_scale.attr,
+ &dev_attr_sampling_frequency.attr,
+ &dev_attr_compass_matrix.attr,
+ NULL,
+};
+
+static const struct attribute_group inv_attribute_group = {
+ .name = "ak89xx",
+ .attrs = inv_ak89xx_attributes
+};
+
+static const struct iio_info ak89xx_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &ak89xx_read_raw,
+ .attrs = &inv_attribute_group,
+};
+
+/*constant IIO attribute */
+/**
+ * inv_ak89xx_probe() - probe function.
+ */
+static int inv_ak89xx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct inv_ak89xx_state_s *st;
+ struct iio_dev *indio_dev;
+ int result;
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ result = -ENODEV;
+ goto out_no_free;
+ }
+ indio_dev = iio_allocate_device(sizeof(*st));
+ if (indio_dev == NULL) {
+ result = -ENOMEM;
+ goto out_no_free;
+ }
+ st = iio_priv(indio_dev);
+ st->i2c = client;
+ st->sl_handle = client->adapter;
+ st->plat_data =
+ *(struct mpu_platform_data *)dev_get_platdata(&client->dev);
+ st->i2c_addr = client->addr;
+ st->delay = AK89XX_DEFAULT_DELAY;
+ st->compass_id = id->driver_data;
+ st->compass_scale = 0;
+
+ i2c_set_clientdata(client, indio_dev);
+ result = ak89xx_init(st);
+ if (result)
+ goto out_free;
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->name = id->name;
+ indio_dev->channels = compass_channels;
+ indio_dev->num_channels = ARRAY_SIZE(compass_channels);
+ indio_dev->info = &ak89xx_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->currentmode = INDIO_DIRECT_MODE;
+
+ result = inv_ak89xx_configure_ring(indio_dev);
+ if (result)
+ goto out_free;
+ result = iio_buffer_register(indio_dev, indio_dev->channels,
+ indio_dev->num_channels);
+ if (result)
+ goto out_unreg_ring;
+ result = inv_ak89xx_probe_trigger(indio_dev);
+ if (result)
+ goto out_remove_ring;
+
+ result = iio_device_register(indio_dev);
+ if (result)
+ goto out_remove_trigger;
+ INIT_DELAYED_WORK(&st->work, ak89xx_work_func);
+ pr_info("%s: Probe name %s\n", __func__, id->name);
+ return 0;
+out_remove_trigger:
+ if (indio_dev->modes & INDIO_BUFFER_TRIGGERED)
+ inv_ak89xx_remove_trigger(indio_dev);
+out_remove_ring:
+ iio_buffer_unregister(indio_dev);
+out_unreg_ring:
+ inv_ak89xx_unconfigure_ring(indio_dev);
+out_free:
+ iio_free_device(indio_dev);
+out_no_free:
+ dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+ return -EIO;
+}
+
+/**
+ * inv_ak89xx_remove() - remove function.
+ */
+static int inv_ak89xx_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ cancel_delayed_work_sync(&st->work);
+ iio_device_unregister(indio_dev);
+ inv_ak89xx_remove_trigger(indio_dev);
+ iio_buffer_unregister(indio_dev);
+ inv_ak89xx_unconfigure_ring(indio_dev);
+ iio_free_device(indio_dev);
+
+ dev_info(&client->adapter->dev, "inv-ak89xx-iio module removed.\n");
+ return 0;
+}
+
+static const unsigned short normal_i2c[] = { I2C_CLIENT_END };
+
+/* device id table is used to identify what device can be
+ * supported by this driver
+ */
+static const struct i2c_device_id inv_ak89xx_id[] = {
+ {"akm8975", COMPASS_ID_AK8975},
+ {"akm8972", COMPASS_ID_AK8972},
+ {"akm8963", COMPASS_ID_AK8963},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, inv_ak89xx_id);
+
+static struct i2c_driver inv_ak89xx_driver = {
+ .class = I2C_CLASS_HWMON,
+ .probe = inv_ak89xx_probe,
+ .remove = inv_ak89xx_remove,
+ .id_table = inv_ak89xx_id,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "inv-ak89xx-iio",
+ },
+ .address_list = normal_i2c,
+};
+
+static int __init inv_ak89xx_init(void)
+{
+ int result = i2c_add_driver(&inv_ak89xx_driver);
+ if (result) {
+ pr_err("%s failed\n", __func__);
+ return result;
+ }
+ return 0;
+}
+
+static void __exit inv_ak89xx_exit(void)
+{
+ i2c_del_driver(&inv_ak89xx_driver);
+}
+
+module_init(inv_ak89xx_init);
+module_exit(inv_ak89xx_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Invensense device driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("inv-ak89xx-iio");
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ak89xx_iio.h b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_iio.h
new file mode 100644
index 000000000000..9a9f14a73ec5
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_iio.h
@@ -0,0 +1,144 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*/
+#ifndef _INV_AK89XX_IIO_H_
+#define _INV_AK89XX_IIO_H_
+
+#include <linux/i2c.h>
+#include <linux/kfifo.h>
+#include <linux/miscdevice.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include <linux/mpu.h>
+
+#include "iio.h"
+#include "buffer.h"
+#include "trigger.h"
+
+/**
+ * struct inv_ak89xx_state_s - Driver state variables.
+ * @plat_data: board file platform data.
+ * @i2c: i2c client handle.
+ * @trig: not used. for compatibility.
+ * @work: work data structure.
+ * @delay: delay between each scheduled work.
+ * @dev: Represents read-only node for accessing buffered data.
+ * @inv_dev: Handle to input device.
+ * @sl_handle: Handle to I2C port.
+ */
+struct inv_ak89xx_state_s {
+ struct mpu_platform_data plat_data;
+ struct i2c_client *i2c;
+ struct iio_trigger *trig;
+ struct delayed_work work;
+ int delay; /* msec */
+ unsigned char compass_id;
+ int compass_scale; /* for ak8963, 1:16-bit, 0:14-bit */
+ short compass_data[3];
+ u8 asa[3]; /* axis sensitivity adjustment */
+ s64 timestamp;
+ short i2c_addr;
+ void *sl_handle;
+ struct device *inv_dev;
+ struct input_dev *idev;
+};
+
+/* scan element definition */
+enum inv_mpu_scan {
+ INV_AK89XX_SCAN_MAGN_X,
+ INV_AK89XX_SCAN_MAGN_Y,
+ INV_AK89XX_SCAN_MAGN_Z,
+ INV_AK89XX_SCAN_TIMESTAMP,
+};
+
+#define AK89XX_I2C_NAME "ak89xx"
+
+#define SENSOR_DATA_SIZE 8
+#define YPR_DATA_SIZE 12
+#define RWBUF_SIZE 16
+
+#define ACC_DATA_FLAG 0
+#define MAG_DATA_FLAG 1
+#define ORI_DATA_FLAG 2
+#define AKM_NUM_SENSORS 3
+
+#define ACC_DATA_READY (1 << (ACC_DATA_FLAG))
+#define MAG_DATA_READY (1 << (MAG_DATA_FLAG))
+#define ORI_DATA_READY (1 << (ORI_DATA_FLAG))
+
+#define AKM_MINOR_NUMBER 254
+
+/*! \name AK89XX constant definition
+ \anchor AK89XX_Def
+ Constant definitions of the AK89XX.*/
+#define AK89XX_MEASUREMENT_TIME_US 10000
+
+/*! \name AK89XX operation mode
+ \anchor AK89XX_Mode
+ Defines an operation mode of the AK89XX.*/
+/*! @{*/
+#define AK89XX_CNTL_MODE_SNG_MEASURE 0x01
+#define AK89XX_CNTL_MODE_SELF_TEST 0x08
+#define AK89XX_CNTL_MODE_FUSE_ACCESS 0x0F
+#define AK89XX_CNTL_MODE_POWER_DOWN 0x00
+/*! @}*/
+
+/*! \name AK89XX register address
+\anchor AK89XX_REG
+Defines a register address of the AK89XX.*/
+/*! @{*/
+#define AK89XX_REG_WIA 0x00
+#define AK89XX_REG_INFO 0x01
+#define AK89XX_REG_ST1 0x02
+#define AK89XX_REG_HXL 0x03
+#define AK89XX_REG_HXH 0x04
+#define AK89XX_REG_HYL 0x05
+#define AK89XX_REG_HYH 0x06
+#define AK89XX_REG_HZL 0x07
+#define AK89XX_REG_HZH 0x08
+#define AK89XX_REG_ST2 0x09
+#define AK89XX_REG_CNTL 0x0A
+#define AK89XX_REG_RSV 0x0B
+#define AK89XX_REG_ASTC 0x0C
+#define AK89XX_REG_TS1 0x0D
+#define AK89XX_REG_TS2 0x0E
+#define AK89XX_REG_I2CDIS 0x0F
+/*! @}*/
+
+/*! \name AK89XX fuse-rom address
+\anchor AK89XX_FUSE
+Defines a read-only address of the fuse ROM of the AK89XX.*/
+/*! @{*/
+#define AK89XX_FUSE_ASAX 0x10
+#define AK89XX_FUSE_ASAY 0x11
+#define AK89XX_FUSE_ASAZ 0x12
+/*! @}*/
+
+#define AK89XX_MAX_DELAY (200)
+#define AK89XX_MIN_DELAY (10)
+#define AK89XX_DEFAULT_DELAY (100)
+
+#define INV_ERROR_COMPASS_DATA_OVERFLOW (-1)
+#define INV_ERROR_COMPASS_DATA_NOT_READY (-2)
+
+int inv_ak89xx_configure_ring(struct iio_dev *indio_dev);
+void inv_ak89xx_unconfigure_ring(struct iio_dev *indio_dev);
+int inv_ak89xx_probe_trigger(struct iio_dev *indio_dev);
+void inv_ak89xx_remove_trigger(struct iio_dev *indio_dev);
+void set_ak89xx_enable(struct iio_dev *indio_dev, bool enable);
+int ak89xx_read_raw_data(struct inv_ak89xx_state_s *st,
+ short dat[3]);
+void inv_read_ak89xx_fifo(struct iio_dev *indio_dev);
+int ak89xx_read(struct inv_ak89xx_state_s *st, short rawfixed[3]);
+
+#endif
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ak89xx_ring.c b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_ring.c
new file mode 100644
index 000000000000..a8c1090bed5b
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_ring.c
@@ -0,0 +1,138 @@
+/*
+* Copyright (C) 2013 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*/
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+
+#include "iio.h"
+#include "kfifo_buf.h"
+#include "trigger_consumer.h"
+#include "sysfs.h"
+
+#include "inv_ak89xx_iio.h"
+
+static int put_scan_to_buf(struct iio_dev *indio_dev, unsigned char *d,
+ short *s, int scan_index)
+{
+ struct iio_buffer *ring = indio_dev->buffer;
+ int st;
+ int i, d_ind;
+
+ d_ind = 0;
+ for (i = 0; i < 3; i++) {
+ st = iio_scan_mask_query(indio_dev, ring, scan_index + i);
+ if (st) {
+ memcpy(&d[d_ind], &s[i], sizeof(s[i]));
+ d_ind += sizeof(s[i]);
+ }
+ }
+
+ return d_ind;
+}
+
+/**
+ * inv_read_ak89xx_fifo() - Transfer data from FIFO to ring buffer.
+ */
+void inv_read_ak89xx_fifo(struct iio_dev *indio_dev)
+{
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int d_ind;
+ s8 *tmp;
+ s64 tmp_buf[2];
+
+ if (!ak89xx_read(st, st->compass_data)) {
+ st->compass_data[0] = (short)(((int)st->compass_data[0] * (st->asa[0] + 128)) >> 8);
+ st->compass_data[1] = (short)(((int)st->compass_data[1] * (st->asa[1] + 128)) >> 8);
+ st->compass_data[2] = (short)(((int)st->compass_data[2] * (st->asa[2] + 128)) >> 8);
+ tmp = (u8 *)tmp_buf;
+ d_ind = put_scan_to_buf(indio_dev, tmp, st->compass_data,
+ INV_AK89XX_SCAN_MAGN_X);
+ if (ring->scan_timestamp)
+ tmp_buf[(d_ind + 7)/8] = st->timestamp;
+ ring->access->store_to(indio_dev->buffer, tmp, st->timestamp);
+ }
+}
+
+void inv_ak89xx_unconfigure_ring(struct iio_dev *indio_dev)
+{
+ iio_kfifo_free(indio_dev->buffer);
+};
+
+static int inv_ak89xx_postenable(struct iio_dev *indio_dev)
+{
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+
+ /* when all the outputs are disabled, even though buffer/enable is on,
+ do nothing */
+ if (!(iio_scan_mask_query(indio_dev, ring, INV_AK89XX_SCAN_MAGN_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_AK89XX_SCAN_MAGN_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_AK89XX_SCAN_MAGN_Z)))
+ return 0;
+
+ set_ak89xx_enable(indio_dev, true);
+ schedule_delayed_work(&st->work, msecs_to_jiffies(st->delay));
+
+ return 0;
+}
+
+static int inv_ak89xx_predisable(struct iio_dev *indio_dev)
+{
+ struct iio_buffer *ring = indio_dev->buffer;
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+
+ cancel_delayed_work_sync(&st->work);
+ clear_bit(INV_AK89XX_SCAN_MAGN_X, ring->scan_mask);
+ clear_bit(INV_AK89XX_SCAN_MAGN_Y, ring->scan_mask);
+ clear_bit(INV_AK89XX_SCAN_MAGN_Z, ring->scan_mask);
+ set_ak89xx_enable(indio_dev, false);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops inv_ak89xx_ring_setup_ops = {
+ .preenable = &iio_sw_buffer_preenable,
+ .postenable = &inv_ak89xx_postenable,
+ .predisable = &inv_ak89xx_predisable,
+};
+
+int inv_ak89xx_configure_ring(struct iio_dev *indio_dev)
+{
+ int ret = 0;
+ struct iio_buffer *ring;
+
+ ring = iio_kfifo_allocate(indio_dev);
+ if (!ring) {
+ ret = -ENOMEM;
+ return ret;
+ }
+ indio_dev->buffer = ring;
+ /* setup ring buffer */
+ ring->scan_timestamp = true;
+ indio_dev->setup_ops = &inv_ak89xx_ring_setup_ops;
+
+ indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
+ return 0;
+}
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ak89xx_trigger.c b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_trigger.c
new file mode 100644
index 000000000000..04c77ab5c79d
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ak89xx_trigger.c
@@ -0,0 +1,75 @@
+/*
+* Copyright (C) 2013 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*/
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "iio.h"
+#include "sysfs.h"
+#include "trigger.h"
+#include "inv_ak89xx_iio.h"
+
+static const struct iio_trigger_ops inv_ak89xx_trigger_ops = {
+ .owner = THIS_MODULE,
+};
+
+int inv_ak89xx_probe_trigger(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+
+ st->trig = iio_allocate_trigger("%s-dev%d",
+ indio_dev->name,
+ indio_dev->id);
+ if (st->trig == NULL) {
+ ret = -ENOMEM;
+ goto error_ret;
+ }
+ /* select default trigger */
+ st->trig->dev.parent = &st->i2c->dev;
+ st->trig->private_data = indio_dev;
+ st->trig->ops = &inv_ak89xx_trigger_ops;
+ ret = iio_trigger_register(st->trig);
+
+ /* select default trigger */
+ indio_dev->trig = st->trig;
+ if (ret)
+ goto error_free_trig;
+
+ return 0;
+
+error_free_trig:
+ iio_free_trigger(st->trig);
+error_ret:
+ return ret;
+}
+
+void inv_ak89xx_remove_trigger(struct iio_dev *indio_dev)
+{
+ struct inv_ak89xx_state_s *st = iio_priv(indio_dev);
+
+ iio_trigger_unregister(st->trig);
+ iio_free_trigger(st->trig);
+}
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_core.c b/drivers/iio/magnetometer/inv_compass/inv_ami306_core.c
new file mode 100644
index 000000000000..612ba72b59e9
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_core.c
@@ -0,0 +1,570 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_ami306_core.c
+ * @brief Invensense implementation for AMI306
+ * @details This driver currently works for the AMI306
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "inv_ami306_iio.h"
+#include "sysfs.h"
+#include "inv_test/inv_counters.h"
+
+static unsigned char late_initialize = true;
+
+s32 i2c_write(const struct i2c_client *client,
+ u8 command, u8 length, const u8 *values)
+{
+ INV_I2C_INC_COMPASSWRITE(3);
+ return i2c_smbus_write_i2c_block_data(client, command, length, values);
+}
+
+s32 i2c_read(const struct i2c_client *client,
+ u8 command, u8 length, u8 *values)
+{
+ INV_I2C_INC_COMPASSWRITE(3);
+ INV_I2C_INC_COMPASSREAD(length);
+ return i2c_smbus_read_i2c_block_data(client, command, length, values);
+}
+
+static int ami306_read_param(struct inv_ami306_state_s *st)
+{
+ int result = 0;
+ unsigned char regs[AMI_PARAM_LEN];
+ struct ami_sensor_parametor *param = &st->param;
+
+ result = i2c_read(st->i2c, REG_AMI_SENX,
+ AMI_PARAM_LEN, regs);
+ if (result < 0)
+ return result;
+
+ /* Little endian 16 bit registers */
+ param->m_gain.x = le16_to_cpup((__le16 *)(&regs[0]));
+ param->m_gain.y = le16_to_cpup((__le16 *)(&regs[2]));
+ param->m_gain.z = le16_to_cpup((__le16 *)(&regs[4]));
+
+ param->m_interference.xy = regs[7];
+ param->m_interference.xz = regs[6];
+ param->m_interference.yx = regs[9];
+ param->m_interference.yz = regs[8];
+ param->m_interference.zx = regs[11];
+ param->m_interference.zy = regs[10];
+
+ param->m_offset.x = AMI_STANDARD_OFFSET;
+ param->m_offset.y = AMI_STANDARD_OFFSET;
+ param->m_offset.z = AMI_STANDARD_OFFSET;
+
+ param->m_gain_cor.x = AMI_GAIN_COR_DEFAULT;
+ param->m_gain_cor.y = AMI_GAIN_COR_DEFAULT;
+ param->m_gain_cor.z = AMI_GAIN_COR_DEFAULT;
+
+ return 0;
+}
+
+static int ami306_write_offset(const struct i2c_client *client,
+ unsigned char *fine)
+{
+ int result = 0;
+ unsigned char dat[3];
+ dat[0] = (0x7f & fine[0]);
+ dat[1] = 0;
+ result = i2c_write(client, REG_AMI_OFFX, 2, dat);
+ dat[0] = (0x7f & fine[1]);
+ dat[1] = 0;
+ result = i2c_write(client, REG_AMI_OFFY, 2, dat);
+ dat[0] = (0x7f & fine[2]);
+ dat[1] = 0;
+ result = i2c_write(client, REG_AMI_OFFZ, 2, dat);
+
+ return result;
+}
+
+static int ami306_wait_data_ready(struct inv_ami306_state_s *st,
+ unsigned long usecs, unsigned long times)
+{
+ int result = 0;
+ unsigned char buf;
+
+ for (; 0 < times; --times) {
+ udelay(usecs);
+ result = i2c_read(st->i2c, REG_AMI_STA1, 1, &buf);
+ if (result < 0)
+ return INV_ERROR_COMPASS_DATA_NOT_READY;
+ if (buf & AMI_STA1_DRDY_BIT)
+ return 0;
+ else if (buf & AMI_STA1_DOR_BIT)
+ return INV_ERROR_COMPASS_DATA_OVERFLOW;
+ }
+
+ return INV_ERROR_COMPASS_DATA_NOT_READY;
+}
+int ami306_read_raw_data(struct inv_ami306_state_s *st,
+ short dat[3])
+{
+ int result;
+ unsigned char buf[6];
+ result = i2c_read(st->i2c, REG_AMI_DATAX, sizeof(buf), buf);
+ if (result < 0)
+ return result;
+ dat[0] = le16_to_cpup((__le16 *)(&buf[0]));
+ dat[1] = le16_to_cpup((__le16 *)(&buf[2]));
+ dat[2] = le16_to_cpup((__le16 *)(&buf[4]));
+
+ return 0;
+}
+
+#define AMI_WAIT_DATAREADY_RETRY 3 /* retry times */
+#define AMI_DRDYWAIT 800 /* u(micro) sec */
+static int ami306_force_measurement(struct inv_ami306_state_s *st,
+ short ver[3])
+{
+ int result;
+ int status;
+ char buf;
+ buf = AMI_CTRL3_FORCE_BIT;
+ result = i2c_write(st->i2c, REG_AMI_CTRL3, 1, &buf);
+ if (result < 0)
+ return result;
+
+ result = ami306_wait_data_ready(st,
+ AMI_DRDYWAIT, AMI_WAIT_DATAREADY_RETRY);
+ if (result && result != INV_ERROR_COMPASS_DATA_OVERFLOW)
+ return result;
+ /* READ DATA X,Y,Z */
+ status = ami306_read_raw_data(st, ver);
+ if (status)
+ return status;
+
+ return result;
+}
+
+static int ami306_initial_b0_adjust(struct inv_ami306_state_s *st)
+{
+ int result;
+ unsigned char fine[3] = { 0 };
+ short data[3];
+ int diff[3] = { 0x7fff, 0x7fff, 0x7fff };
+ int fn = 0;
+ int ax = 0;
+ unsigned char buf[3];
+
+ buf[0] = AMI_CTRL2_DREN;
+ result = i2c_write(st->i2c, REG_AMI_CTRL2, 1, buf);
+ if (result)
+ return result;
+
+ buf[0] = AMI_CTRL4_HS & 0xFF;
+ buf[1] = (AMI_CTRL4_HS >> 8) & 0xFF;
+ result = i2c_write(st->i2c, REG_AMI_CTRL4, 2, buf);
+ if (result < 0)
+ return result;
+
+ for (fn = 0; fn < AMI_FINE_MAX; ++fn) { /* fine 0 -> 95 */
+ fine[0] = fine[1] = fine[2] = fn;
+ result = ami306_write_offset(st->i2c, fine);
+ if (result)
+ return result;
+
+ result = ami306_force_measurement(st, data);
+ if (result)
+ return result;
+
+ for (ax = 0; ax < 3; ax++) {
+ /* search point most close to zero. */
+ if (diff[ax] > abs(data[ax])) {
+ st->fine[ax] = fn;
+ diff[ax] = abs(data[ax]);
+ }
+ }
+ }
+ result = ami306_write_offset(st->i2c, st->fine);
+ if (result)
+ return result;
+
+ /* Software Reset */
+ buf[0] = AMI_CTRL3_SRST_BIT;
+ result = i2c_write(st->i2c, REG_AMI_CTRL3, 1, buf);
+ if (result < 0)
+ return result;
+ else
+ return 0;
+}
+
+static int ami306_start_sensor(struct inv_ami306_state_s *st)
+{
+ int result = 0;
+ unsigned char buf[2];
+
+ /* Step 1 */
+ buf[0] = (AMI_CTRL1_PC1 | AMI_CTRL1_FS1_FORCE);
+ result = i2c_write(st->i2c, REG_AMI_CTRL1, 1, buf);
+ if (result < 0)
+ return result;
+ /* Step 2 */
+ buf[0] = AMI_CTRL2_DREN;
+ result = i2c_write(st->i2c, REG_AMI_CTRL2, 1, buf);
+ if (result < 0)
+ return result;
+ /* Step 3 */
+ buf[0] = (AMI_CTRL4_HS & 0xFF);
+ buf[1] = (AMI_CTRL4_HS >> 8) & 0xFF;
+
+ result = i2c_write(st->i2c, REG_AMI_CTRL4, 2, buf);
+ if (result < 0)
+ return result;
+
+ /* Step 4 */
+ result = ami306_write_offset(st->i2c, st->fine);
+
+ return result;
+}
+
+int set_ami306_enable(struct iio_dev *indio_dev, int state)
+{
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+ int result;
+ char buf;
+
+ buf = (AMI_CTRL1_PC1 | AMI_CTRL1_FS1_FORCE);
+ result = i2c_write(st->i2c, REG_AMI_CTRL1, 1, &buf);
+ if (result < 0)
+ return result;
+
+ result = ami306_read_param(st);
+ if (result)
+ return result;
+ if (late_initialize) {
+ result = ami306_initial_b0_adjust(st);
+ if (result)
+ return result;
+ late_initialize = false;
+ }
+ result = ami306_start_sensor(st);
+ if (result)
+ return result;
+ buf = AMI_CTRL3_FORCE_BIT;
+ st->timestamp = iio_get_time_ns();
+ result = i2c_write(st->i2c, REG_AMI_CTRL3, 1, &buf);
+ if (result)
+ return result;
+
+ return 0;
+}
+
+/**
+ * ami306_read_raw() - read raw method.
+ */
+static int ami306_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask) {
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case 0:
+ if (!(iio_buffer_enabled(indio_dev)))
+ return -EINVAL;
+ if (chan->type == IIO_MAGN) {
+ *val = st->compass_data[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_MAGN) {
+ *val = AMI_SCALE;
+ return IIO_VAL_INT;
+ }
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * inv_compass_matrix_show() - show orientation matrix
+ */
+static ssize_t inv_compass_matrix_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ signed char *m;
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+ m = st->plat_data.orientation;
+ return sprintf(buf,
+ "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+}
+
+static ssize_t ami306_rate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long data;
+ int error;
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+
+ error = kstrtoul(buf, 10, &data);
+ if (error)
+ return error;
+ if (0 == data)
+ return -EINVAL;
+ /* transform rate to delay in ms */
+ data = 1000 / data;
+ if (data > AMI_MAX_DELAY)
+ data = AMI_MAX_DELAY;
+ if (data < AMI_MIN_DELAY)
+ data = AMI_MIN_DELAY;
+ st->delay = data;
+ return count;
+}
+
+static ssize_t ami306_rate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+ /* transform delay in ms to rate */
+ return sprintf(buf, "%d\n", 1000 / st->delay);
+}
+
+
+static void ami306_work_func(struct work_struct *work)
+{
+ struct inv_ami306_state_s *st =
+ container_of((struct delayed_work *)work,
+ struct inv_ami306_state_s, work);
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ unsigned long delay = msecs_to_jiffies(st->delay);
+
+ mutex_lock(&indio_dev->mlock);
+ if (!(iio_buffer_enabled(indio_dev)))
+ goto error_ret;
+
+ st->timestamp = iio_get_time_ns();
+ schedule_delayed_work(&st->work, delay);
+ inv_read_ami306_fifo(indio_dev);
+ INV_I2C_INC_COMPASSIRQ();
+
+error_ret:
+ mutex_unlock(&indio_dev->mlock);
+}
+
+static const struct iio_chan_spec compass_channels[] = {
+ {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_AMI306_SCAN_MAGN_X,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Y,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_AMI306_SCAN_MAGN_Y,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Z,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_AMI306_SCAN_MAGN_Z,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(INV_AMI306_SCAN_TIMESTAMP)
+};
+
+static DEVICE_ATTR(compass_matrix, S_IRUGO, inv_compass_matrix_show, NULL);
+static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, ami306_rate_show,
+ ami306_rate_store);
+
+static struct attribute *inv_ami306_attributes[] = {
+ &dev_attr_compass_matrix.attr,
+ &dev_attr_sampling_frequency.attr,
+ NULL,
+};
+static const struct attribute_group inv_attribute_group = {
+ .name = "ami306",
+ .attrs = inv_ami306_attributes
+};
+
+static const struct iio_info ami306_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &ami306_read_raw,
+ .attrs = &inv_attribute_group,
+};
+
+/*constant IIO attribute */
+/**
+ * inv_ami306_probe() - probe function.
+ */
+static int inv_ami306_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct inv_ami306_state_s *st;
+ struct iio_dev *indio_dev;
+ int result;
+ char data;
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ result = -ENODEV;
+ goto out_no_free;
+ }
+ indio_dev = iio_allocate_device(sizeof(*st));
+ if (indio_dev == NULL) {
+ result = -ENOMEM;
+ goto out_no_free;
+ }
+ st = iio_priv(indio_dev);
+ st->i2c = client;
+ st->plat_data =
+ *(struct mpu_platform_data *)dev_get_platdata(&client->dev);
+ st->delay = 10;
+
+ /* Make state variables available to all _show and _store functions. */
+ i2c_set_clientdata(client, indio_dev);
+ result = i2c_read(st->i2c, REG_AMI_WIA, 1, &data);
+ if (result < 0)
+ goto out_free;
+ if (data != DATA_WIA)
+ goto out_free;
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->name = id->name;
+ indio_dev->channels = compass_channels;
+ indio_dev->num_channels = ARRAY_SIZE(compass_channels);
+ indio_dev->info = &ami306_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->currentmode = INDIO_DIRECT_MODE;
+
+ result = inv_ami306_configure_ring(indio_dev);
+ if (result)
+ goto out_free;
+ result = iio_buffer_register(indio_dev, indio_dev->channels,
+ indio_dev->num_channels);
+ if (result)
+ goto out_unreg_ring;
+ result = inv_ami306_probe_trigger(indio_dev);
+ if (result)
+ goto out_remove_ring;
+
+ result = iio_device_register(indio_dev);
+ if (result)
+ goto out_remove_trigger;
+ INIT_DELAYED_WORK(&st->work, ami306_work_func);
+ pr_info("%s: Probe name %s\n", __func__, id->name);
+ return 0;
+out_remove_trigger:
+ if (indio_dev->modes & INDIO_BUFFER_TRIGGERED)
+ inv_ami306_remove_trigger(indio_dev);
+out_remove_ring:
+ iio_buffer_unregister(indio_dev);
+out_unreg_ring:
+ inv_ami306_unconfigure_ring(indio_dev);
+out_free:
+ iio_free_device(indio_dev);
+out_no_free:
+ dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+ return -EIO;
+}
+
+/**
+ * inv_ami306_remove() - remove function.
+ */
+static int inv_ami306_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+ cancel_delayed_work_sync(&st->work);
+ iio_device_unregister(indio_dev);
+ inv_ami306_remove_trigger(indio_dev);
+ iio_buffer_unregister(indio_dev);
+ inv_ami306_unconfigure_ring(indio_dev);
+ iio_free_device(indio_dev);
+
+ dev_info(&client->adapter->dev, "inv-ami306-iio module removed.\n");
+ return 0;
+}
+static const unsigned short normal_i2c[] = { I2C_CLIENT_END };
+/* device id table is used to identify what device can be
+ * supported by this driver
+ */
+static const struct i2c_device_id inv_ami306_id[] = {
+ {"ami306", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, inv_ami306_id);
+
+static struct i2c_driver inv_ami306_driver = {
+ .class = I2C_CLASS_HWMON,
+ .probe = inv_ami306_probe,
+ .remove = inv_ami306_remove,
+ .id_table = inv_ami306_id,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "inv-ami306-iio",
+ },
+ .address_list = normal_i2c,
+};
+
+static int __init inv_ami306_init(void)
+{
+ int result = i2c_add_driver(&inv_ami306_driver);
+ if (result) {
+ pr_err("%s failed\n", __func__);
+ return result;
+ }
+ return 0;
+}
+
+static void __exit inv_ami306_exit(void)
+{
+ i2c_del_driver(&inv_ami306_driver);
+}
+
+module_init(inv_ami306_init);
+module_exit(inv_ami306_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Invensense device driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("inv-ami306-iio");
+/**
+ * @}
+ */
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_iio.h b/drivers/iio/magnetometer/inv_compass/inv_ami306_iio.h
new file mode 100644
index 000000000000..fa4f4ee1e5da
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_iio.h
@@ -0,0 +1,159 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_ami306_iio.h
+ * @brief Struct definitions for the Invensense implementation
+ * of ami306 driver.
+ */
+
+#ifndef _INV_GYRO_H_
+#define _INV_GYRO_H_
+
+#include <linux/i2c.h>
+#include <linux/kfifo.h>
+#include <linux/miscdevice.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include <linux/mpu.h>
+
+#include "iio.h"
+#include "buffer.h"
+#include "trigger.h"
+
+/** axis sensitivity(gain) calibration parameter information */
+struct ami_vector3d {
+ signed short x; /**< X-axis */
+ signed short y; /**< Y-axis */
+ signed short z; /**< Z-axis */
+};
+
+/** axis interference information */
+struct ami_interference {
+ /**< Y-axis magnetic field for X-axis correction value */
+ signed short xy;
+ /**< Z-axis magnetic field for X-axis correction value */
+ signed short xz;
+ /**< X-axis magnetic field for Y-axis correction value */
+ signed short yx;
+ /**< Z-axis magnetic field for Y-axis correction value */
+ signed short yz;
+ /**< X-axis magnetic field for Z-axis correction value */
+ signed short zx;
+ /**< Y-axis magnetic field for Z-axis correction value */
+ signed short zy;
+};
+
+/** sensor calibration Parameter information */
+struct ami_sensor_parametor {
+ /**< geomagnetic field sensor gain */
+ struct ami_vector3d m_gain;
+ /**< geomagnetic field sensor gain correction parameter */
+ struct ami_vector3d m_gain_cor;
+ /**< geomagnetic field sensor offset */
+ struct ami_vector3d m_offset;
+ /**< geomagnetic field sensor axis interference parameter */
+ struct ami_interference m_interference;
+};
+
+/**
+ * struct inv_ami306_state_s - Driver state variables.
+ * @plat_data: board file platform data.
+ * @i2c: i2c client handle.
+ * @trig: not used. for compatibility.
+ * @param: ami specific sensor data.
+ * @work: work data structure.
+ * @delay: delay between each scheduled work.
+ * @fine: fine tunign parameters.
+ * @compass_data: compass data store.
+ * @timestamp: time stamp.
+ */
+struct inv_ami306_state_s {
+ struct mpu_platform_data plat_data;
+ struct i2c_client *i2c;
+ struct iio_trigger *trig;
+ struct ami_sensor_parametor param;
+ struct delayed_work work;
+ int delay;
+ s8 fine[3];
+ short compass_data[3];
+ s64 timestamp;
+};
+/* scan element definition */
+enum inv_mpu_scan {
+ INV_AMI306_SCAN_MAGN_X,
+ INV_AMI306_SCAN_MAGN_Y,
+ INV_AMI306_SCAN_MAGN_Z,
+ INV_AMI306_SCAN_TIMESTAMP,
+};
+
+#define REG_AMI_WIA 0x0f
+#define REG_AMI_DATAX 0x10
+#define REG_AMI_STA1 0x18
+#define REG_AMI_CTRL1 0x1b
+#define REG_AMI_CTRL2 0x1c
+#define REG_AMI_CTRL3 0x1d
+#define REG_AMI_B0X 0x20
+#define REG_AMI_B0Y 0x22
+#define REG_AMI_B0Z 0x24
+#define REG_AMI_CTRL5 0x40
+#define REG_AMI_CTRL4 0x5c
+#define REG_AMI_TEMP 0x60
+#define REG_AMI_SENX 0x96
+#define REG_AMI_OFFX 0x6c
+#define REG_AMI_OFFY 0x72
+#define REG_AMI_OFFZ 0x78
+
+
+#define DATA_WIA 0x46
+#define AMI_CTRL1_PC1 0x80
+#define AMI_CTRL1_FS1_FORCE 0x02
+#define AMI_CTRL1_ODR1 0x10
+#define AMI_CTRL2_DREN 0x08
+#define AMI_CTRL2_DRP 0x04
+#define AMI_CTRL3_FORCE_BIT 0x40
+#define AMI_CTRL3_B0_LO_BIT 0x10
+#define AMI_CTRL3_SRST_BIT 0x80
+#define AMI_CTRL4_HS 0xa07e
+#define AMI_CTRL4_AB 0x0001
+#define AMI_STA1_DRDY_BIT 0x40
+#define AMI_STA1_DOR_BIT 0x20
+
+#define AMI_PARAM_LEN 12
+#define AMI_STANDARD_OFFSET 0x800
+#define AMI_GAIN_COR_DEFAULT 1000
+#define AMI_FINE_MAX 96
+#define AMI_MAX_DELAY 1000
+#define AMI_MIN_DELAY 10
+#define AMI_SCALE (5461 * (1<<15))
+
+#define INV_ERROR_COMPASS_DATA_OVERFLOW (-1)
+#define INV_ERROR_COMPASS_DATA_NOT_READY (-2)
+
+
+int inv_ami306_configure_ring(struct iio_dev *indio_dev);
+void inv_ami306_unconfigure_ring(struct iio_dev *indio_dev);
+int inv_ami306_probe_trigger(struct iio_dev *indio_dev);
+void inv_ami306_remove_trigger(struct iio_dev *indio_dev);
+int set_ami306_enable(struct iio_dev *indio_dev, int state);
+int ami306_read_raw_data(struct inv_ami306_state_s *st,
+ short dat[3]);
+int inv_read_ami306_fifo(struct iio_dev *indio_dev);
+
+#endif /* #ifndef _INV_GYRO_H_ */
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_ring.c b/drivers/iio/magnetometer/inv_compass/inv_ami306_ring.c
new file mode 100644
index 000000000000..ed91cf49516f
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_ring.c
@@ -0,0 +1,163 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_ami306_ring.c
+ * @brief Invensense implementation for AMI306
+ * @details This driver currently works for the AMI306
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+
+#include "iio.h"
+#include "kfifo_buf.h"
+#include "trigger_consumer.h"
+#include "sysfs.h"
+
+#include "inv_ami306_iio.h"
+
+static int put_scan_to_buf(struct iio_dev *indio_dev, unsigned char *d,
+ short *s, int scan_index) {
+ struct iio_buffer *ring = indio_dev->buffer;
+ int st;
+ int i, d_ind;
+ d_ind = 0;
+ for (i = 0; i < 3; i++) {
+ st = iio_scan_mask_query(indio_dev, ring, scan_index + i);
+ if (st) {
+ memcpy(&d[d_ind], &s[i], sizeof(s[i]));
+ d_ind += sizeof(s[i]);
+ }
+ }
+ return d_ind;
+}
+
+/**
+ * inv_read_fifo() - Transfer data from FIFO to ring buffer.
+ */
+int inv_read_ami306_fifo(struct iio_dev *indio_dev)
+{
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int result, status, d_ind;
+ char b;
+ char *tmp;
+ s64 tmp_buf[2];
+
+ result = i2c_smbus_read_i2c_block_data(st->i2c, REG_AMI_STA1, 1, &b);
+ if (result < 0)
+ goto end_session;
+ if (b & AMI_STA1_DRDY_BIT) {
+ status = ami306_read_raw_data(st, st->compass_data);
+ if (status) {
+ pr_err("error reading raw\n");
+ goto end_session;
+ }
+ tmp = (unsigned char *)tmp_buf;
+ d_ind = put_scan_to_buf(indio_dev, tmp, st->compass_data,
+ INV_AMI306_SCAN_MAGN_X);
+ if (ring->scan_timestamp)
+ tmp_buf[(d_ind + 7)/8] = st->timestamp;
+ ring->access->store_to(indio_dev->buffer, tmp, st->timestamp);
+ } else if (b & AMI_STA1_DOR_BIT)
+ pr_err("not ready\n");
+end_session:
+ b = AMI_CTRL3_FORCE_BIT;
+ result = i2c_smbus_write_i2c_block_data(st->i2c, REG_AMI_CTRL3, 1, &b);
+
+ return IRQ_HANDLED;
+}
+
+void inv_ami306_unconfigure_ring(struct iio_dev *indio_dev)
+{
+ iio_kfifo_free(indio_dev->buffer);
+};
+static int inv_ami306_postenable(struct iio_dev *indio_dev)
+{
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int result;
+
+ /* when all the outputs are disabled, even though buffer/enable is on,
+ do nothing */
+ if (!(iio_scan_mask_query(indio_dev, ring, INV_AMI306_SCAN_MAGN_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_AMI306_SCAN_MAGN_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_AMI306_SCAN_MAGN_Z)))
+ return 0;
+
+ result = set_ami306_enable(indio_dev, true);
+ if (result)
+ return result;
+ schedule_delayed_work(&st->work, msecs_to_jiffies(st->delay));
+
+ return 0;
+}
+
+static int inv_ami306_predisable(struct iio_dev *indio_dev)
+{
+ struct iio_buffer *ring = indio_dev->buffer;
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+
+ cancel_delayed_work_sync(&st->work);
+ clear_bit(INV_AMI306_SCAN_MAGN_X, ring->scan_mask);
+ clear_bit(INV_AMI306_SCAN_MAGN_Y, ring->scan_mask);
+ clear_bit(INV_AMI306_SCAN_MAGN_Z, ring->scan_mask);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops inv_ami306_ring_setup_ops = {
+ .preenable = &iio_sw_buffer_preenable,
+ .postenable = &inv_ami306_postenable,
+ .predisable = &inv_ami306_predisable,
+};
+
+int inv_ami306_configure_ring(struct iio_dev *indio_dev)
+{
+ int ret = 0;
+ struct iio_buffer *ring;
+
+ ring = iio_kfifo_allocate(indio_dev);
+ if (!ring) {
+ ret = -ENOMEM;
+ return ret;
+ }
+ indio_dev->buffer = ring;
+ /* setup ring buffer */
+ ring->scan_timestamp = true;
+ indio_dev->setup_ops = &inv_ami306_ring_setup_ops;
+
+ indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
+ return 0;
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_trigger.c b/drivers/iio/magnetometer/inv_compass/inv_ami306_trigger.c
new file mode 100644
index 000000000000..f7fe59ef5dff
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_trigger.c
@@ -0,0 +1,90 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_ami306_trigger.c
+ * @brief Invensense implementation for AMI306
+ * @details This driver currently works for the AMI306
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "iio.h"
+#include "sysfs.h"
+#include "trigger.h"
+#include "inv_ami306_iio.h"
+
+static const struct iio_trigger_ops inv_ami306_trigger_ops = {
+ .owner = THIS_MODULE,
+};
+
+int inv_ami306_probe_trigger(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+
+ st->trig = iio_allocate_trigger("%s-dev%d",
+ indio_dev->name,
+ indio_dev->id);
+ if (st->trig == NULL) {
+ ret = -ENOMEM;
+ goto error_ret;
+ }
+ /* select default trigger */
+ st->trig->dev.parent = &st->i2c->dev;
+ st->trig->private_data = indio_dev;
+ st->trig->ops = &inv_ami306_trigger_ops;
+ ret = iio_trigger_register(st->trig);
+
+ /* select default trigger */
+ indio_dev->trig = st->trig;
+ if (ret)
+ goto error_free_trig;
+
+ return 0;
+
+error_free_trig:
+ iio_free_trigger(st->trig);
+error_ret:
+ return ret;
+}
+
+void inv_ami306_remove_trigger(struct iio_dev *indio_dev)
+{
+ struct inv_ami306_state_s *st = iio_priv(indio_dev);
+
+ iio_trigger_unregister(st->trig);
+ iio_free_trigger(st->trig);
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c b/drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c
new file mode 100644
index 000000000000..6af420bb5cf1
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c
@@ -0,0 +1,969 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_yas53x_core.c
+ * @brief Invensense implementation for yas530/yas532/yas533.
+ * @details This driver currently works for yas530/yas532/yas533.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "inv_yas53x_iio.h"
+#include "sysfs.h"
+#include "inv_test/inv_counters.h"
+
+/* -------------------------------------------------------------------------- */
+static int Cx, Cy1, Cy2;
+static int /*a1, */ a2, a3, a4, a5, a6, a7, a8, a9;
+static int k;
+
+static u8 dx, dy1, dy2;
+static u8 d2, d3, d4, d5, d6, d7, d8, d9, d0;
+static u8 dck, ver;
+
+/**
+ * inv_serial_read() - Read one or more bytes from the device registers.
+ * @st: Device driver instance.
+ * @reg: First device register to be read from.
+ * @length: Number of bytes to read.
+ * @data: Data read from device.
+ * NOTE: The slave register will not increment when reading from the FIFO.
+ */
+int inv_serial_read(struct inv_compass_state *st, u8 reg, u16 length, u8 *data)
+{
+ int result;
+ INV_I2C_INC_COMPASSWRITE(3);
+ INV_I2C_INC_COMPASSREAD(length);
+ result = i2c_smbus_read_i2c_block_data(st->client, reg, length, data);
+ if (result != length) {
+ if (result < 0)
+ return result;
+ else
+ return -EINVAL;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * inv_serial_single_write() - Write a byte to a device register.
+ * @st: Device driver instance.
+ * @reg: Device register to be written to.
+ * @data: Byte to write to device.
+ */
+int inv_serial_single_write(struct inv_compass_state *st, u8 reg, u8 data)
+{
+ u8 d[1];
+ d[0] = data;
+ INV_I2C_INC_COMPASSWRITE(3);
+
+ return i2c_smbus_write_i2c_block_data(st->client, reg, 1, d);
+}
+
+static int set_hardware_offset(struct inv_compass_state *st,
+ char offset_x, char offset_y1, char offset_y2)
+{
+ char data;
+ int result = 0;
+
+ data = offset_x & 0x3f;
+ result = inv_serial_single_write(st, YAS530_REGADDR_OFFSET_X, data);
+ if (result)
+ return result;
+
+ data = offset_y1 & 0x3f;
+ result = inv_serial_single_write(st, YAS530_REGADDR_OFFSET_Y1, data);
+ if (result)
+ return result;
+
+ data = offset_y2 & 0x3f;
+ result = inv_serial_single_write(st, YAS530_REGADDR_OFFSET_Y2, data);
+ return result;
+}
+
+static int set_measure_command(struct inv_compass_state *st)
+{
+ int result = 0;
+ result = inv_serial_single_write(st,
+ YAS530_REGADDR_MEASURE_COMMAND, 0x01);
+ return result;
+}
+
+static int measure_normal(struct inv_compass_state *st,
+ int *busy, unsigned short *t,
+ unsigned short *x, unsigned short *y1,
+ unsigned short *y2)
+{
+ int result;
+ ktime_t sleeptime;
+ result = set_measure_command(st);
+ sleeptime = ktime_set(0, 2 * NSEC_PER_MSEC);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_hrtimeout(&sleeptime, HRTIMER_MODE_REL);
+
+ result = st->read_data(st, busy, t, x, y1, y2);
+
+ return result;
+}
+
+static int measure_int(struct inv_compass_state *st,
+ int *busy, unsigned short *t,
+ unsigned short *x, unsigned short *y1,
+ unsigned short *y2)
+{
+ int result;
+ if (st->first_read_after_reset) {
+ st->first_read_after_reset = 0;
+ result = 1;
+ } else {
+ result = st->read_data(st, busy, t, x, y1, y2);
+ }
+ result |= set_measure_command(st);
+
+ return result;
+}
+
+static int yas530_read_data(struct inv_compass_state *st,
+ int *busy, u16 *t, u16 *x, u16 *y1, u16 *y2)
+{
+ u8 data[8];
+ u16 b, to, xo, y1o, y2o;
+ int result;
+
+ result = inv_serial_read(st,
+ YAS530_REGADDR_MEASURE_DATA, 8, data);
+ if (result)
+ return result;
+
+ b = (data[0] >> 7) & 0x01;
+ to = (s16)(((data[0] << 2) & 0x1fc) | ((data[1] >> 6) & 0x03));
+ xo = (s16)(((data[2] << 5) & 0xfe0) | ((data[3] >> 3) & 0x1f));
+ y1o = (s16)(((data[4] << 5) & 0xfe0) | ((data[5] >> 3) & 0x1f));
+ y2o = (s16)(((data[6] << 5) & 0xfe0) | ((data[7] >> 3) & 0x1f));
+
+ *busy = b;
+ *t = to;
+ *x = xo;
+ *y1 = y1o;
+ *y2 = y2o;
+
+ return 0;
+}
+
+static int yas532_533_read_data(struct inv_compass_state *st,
+ int *busy, u16 *t, u16 *x, u16 *y1, u16 *y2)
+{
+ u8 data[8];
+ u16 b, to, xo, y1o, y2o;
+ int result;
+
+ result = inv_serial_read(st,
+ YAS530_REGADDR_MEASURE_DATA, 8, data);
+ if (result)
+ return result;
+
+ b = (data[0] >> 7) & 0x01;
+ to = (s16)((((s32)data[0] << 3) & 0x3f8) | ((data[1] >> 5) & 0x07));
+ xo = (s16)((((s32)data[2] << 6) & 0x1fc0) | ((data[3] >> 2) & 0x3f));
+ y1o = (s16)((((s32)data[4] << 6) & 0x1fc0) | ((data[5] >> 2) & 0x3f));
+ y2o = (s16)((((s32)data[6] << 6) & 0x1fc0) | ((data[7] >> 2) & 0x3f));
+
+ *busy = b;
+ *t = to;
+ *x = xo;
+ *y1 = y1o;
+ *y2 = y2o;
+
+ return 0;
+}
+
+static int check_offset(struct inv_compass_state *st,
+ char offset_x, char offset_y1, char offset_y2,
+ int *flag_x, int *flag_y1, int *flag_y2)
+{
+ int result;
+ int busy;
+ short t, x, y1, y2;
+
+ result = set_hardware_offset(st, offset_x, offset_y1, offset_y2);
+ if (result)
+ return result;
+ result = measure_normal(st, &busy, &t, &x, &y1, &y2);
+ if (result)
+ return result;
+ *flag_x = 0;
+ *flag_y1 = 0;
+ *flag_y2 = 0;
+
+ if (x > st->center)
+ *flag_x = 1;
+ if (y1 > st->center)
+ *flag_y1 = 1;
+ if (y2 > st->center)
+ *flag_y2 = 1;
+ if (x < st->center)
+ *flag_x = -1;
+ if (y1 < st->center)
+ *flag_y1 = -1;
+ if (y2 < st->center)
+ *flag_y2 = -1;
+
+ return result;
+}
+
+static int measure_and_set_offset(struct inv_compass_state *st,
+ char *offset)
+{
+ int i;
+ int result = 0;
+ char offset_x = 0, offset_y1 = 0, offset_y2 = 0;
+ int flag_x = 0, flag_y1 = 0, flag_y2 = 0;
+ static const int correct[5] = {16, 8, 4, 2, 1};
+
+ for (i = 0; i < 5; i++) {
+ result = check_offset(st,
+ offset_x, offset_y1, offset_y2,
+ &flag_x, &flag_y1, &flag_y2);
+ if (result)
+ return result;
+ if (flag_x)
+ offset_x += flag_x * correct[i];
+ if (flag_y1)
+ offset_y1 += flag_y1 * correct[i];
+ if (flag_y2)
+ offset_y2 += flag_y2 * correct[i];
+ }
+
+ result = set_hardware_offset(st, offset_x, offset_y1, offset_y2);
+ if (result)
+ return result;
+ offset[0] = offset_x;
+ offset[1] = offset_y1;
+ offset[2] = offset_y2;
+
+ return result;
+}
+
+static void coordinate_conversion(short x, short y1, short y2, short t,
+ int *xo, int *yo, int *zo)
+{
+ int sx, sy1, sy2, sy, sz;
+ int hx, hy, hz;
+
+ sx = x - (Cx * t) / 100;
+ sy1 = y1 - (Cy1 * t) / 100;
+ sy2 = y2 - (Cy2 * t) / 100;
+
+ sy = sy1 - sy2;
+ sz = -sy1 - sy2;
+
+ hx = k * ((100 * sx + a2 * sy + a3 * sz) / 10);
+ hy = k * ((a4 * sx + a5 * sy + a6 * sz) / 10);
+ hz = k * ((a7 * sx + a8 * sy + a9 * sz) / 10);
+
+ *xo = hx;
+ *yo = hy;
+ *zo = hz;
+}
+
+static int get_cal_data_yas532_533(struct inv_compass_state *st)
+{
+ u8 data[YAS_YAS532_533_CAL_DATA_SIZE];
+ int result;
+
+ result = inv_serial_read(st, YAS530_REGADDR_CAL,
+ YAS_YAS532_533_CAL_DATA_SIZE, data);
+ if (result)
+ return result;
+ /* CAL data Second Read */
+ result = inv_serial_read(st, YAS530_REGADDR_CAL,
+ YAS_YAS532_533_CAL_DATA_SIZE, data);
+ if (result)
+ return result;
+
+ dx = data[0];
+ dy1 = data[1];
+ dy2 = data[2];
+ d2 = (data[3] >> 2) & 0x03f;
+ d3 = (u8)(((data[3] << 2) & 0x0c) | ((data[4] >> 6) & 0x03));
+ d4 = (u8)(data[4] & 0x3f);
+ d5 = (data[5] >> 2) & 0x3f;
+ d6 = (u8)(((data[5] << 4) & 0x30) | ((data[6] >> 4) & 0x0f));
+ d7 = (u8)(((data[6] << 3) & 0x78) | ((data[7] >> 5) & 0x07));
+ d8 = (u8)(((data[7] << 1) & 0x3e) | ((data[8] >> 7) & 0x01));
+ d9 = (u8)(((data[8] << 1) & 0xfe) | ((data[9] >> 7) & 0x01));
+ d0 = (u8)((data[9] >> 2) & 0x1f);
+ dck = (u8)(((data[9] << 1) & 0x06) | ((data[10] >> 7) & 0x01));
+ ver = (u8)((data[13]) & 0x01);
+
+ Cx = dx * 10 - 1280;
+ Cy1 = dy1 * 10 - 1280;
+ Cy2 = dy2 * 10 - 1280;
+ a2 = d2 - 32;
+ a3 = d3 - 8;
+ a4 = d4 - 32;
+ a5 = d5 + 38;
+ a6 = d6 - 32;
+ a7 = d7 - 64;
+ a8 = d8 - 32;
+ a9 = d9;
+ k = d0;
+
+ return 0;
+}
+
+static int get_cal_data_yas530(struct inv_compass_state *st)
+{
+ u8 data[YAS_YAS530_CAL_DATA_SIZE];
+ int result;
+ /* CAL data read */
+ result = inv_serial_read(st, YAS530_REGADDR_CAL,
+ YAS_YAS530_CAL_DATA_SIZE, data);
+ if (result)
+ return result;
+ /* CAL data Second Read */
+ result = inv_serial_read(st, YAS530_REGADDR_CAL,
+ YAS_YAS530_CAL_DATA_SIZE, data);
+ if (result)
+ return result;
+ /*Cal data */
+ dx = data[0];
+ dy1 = data[1];
+ dy2 = data[2];
+ d2 = (data[3] >> 2) & 0x03f;
+ d3 = ((data[3] << 2) & 0x0c) | ((data[4] >> 6) & 0x03);
+ d4 = data[4] & 0x3f;
+ d5 = (data[5] >> 2) & 0x3f;
+ d6 = ((data[5] << 4) & 0x30) | ((data[6] >> 4) & 0x0f);
+ d7 = ((data[6] << 3) & 0x78) | ((data[7] >> 5) & 0x07);
+ d8 = ((data[7] << 1) & 0x3e) | ((data[8] >> 7) & 0x01);
+ d9 = ((data[8] << 1) & 0xfe) | ((data[9] >> 7) & 0x01);
+ d0 = (data[9] >> 2) & 0x1f;
+ dck = ((data[9] << 1) & 0x06) | ((data[10] >> 7) & 0x01);
+ ver = (u8)((data[15]) & 0x03);
+
+ /*Correction Data */
+ Cx = (int)dx * 6 - 768;
+ Cy1 = (int)dy1 * 6 - 768;
+ Cy2 = (int)dy2 * 6 - 768;
+ a2 = (int)d2 - 32;
+ a3 = (int)d3 - 8;
+ a4 = (int)d4 - 32;
+ a5 = (int)d5 + 38;
+ a6 = (int)d6 - 32;
+ a7 = (int)d7 - 64;
+ a8 = (int)d8 - 32;
+ a9 = (int)d9;
+ k = (int)d0 + 10;
+
+ return 0;
+}
+
+
+static void thresh_filter_init(struct yas_thresh_filter *thresh_filter,
+ int threshold)
+{
+ thresh_filter->threshold = threshold;
+ thresh_filter->last = 0;
+}
+
+static void
+adaptive_filter_init(struct yas_adaptive_filter *adap_filter, int len,
+ int noise)
+{
+ int i;
+
+ adap_filter->num = 0;
+ adap_filter->index = 0;
+ adap_filter->filter_noise = noise;
+ adap_filter->filter_len = len;
+
+ for (i = 0; i < adap_filter->filter_len; ++i)
+ adap_filter->sequence[i] = 0;
+}
+
+static void yas_init_adap_filter(struct inv_compass_state *st)
+{
+ struct yas_filter *f;
+ int i;
+ int noise[] = {YAS_MAG_DEFAULT_FILTER_NOISE_X,
+ YAS_MAG_DEFAULT_FILTER_NOISE_Y,
+ YAS_MAG_DEFAULT_FILTER_NOISE_Z};
+
+ f = &st->filter;
+ f->filter_len = YAS_MAG_DEFAULT_FILTER_LEN;
+ for (i = 0; i < 3; i++)
+ f->filter_noise[i] = noise[i];
+
+ for (i = 0; i < 3; i++) {
+ adaptive_filter_init(&f->adap_filter[i], f->filter_len,
+ f->filter_noise[i]);
+ thresh_filter_init(&f->thresh_filter[i], f->filter_thresh);
+ }
+}
+
+int yas53x_resume(struct inv_compass_state *st)
+{
+ int result = 0;
+
+ unsigned char dummyData = 0x00;
+ unsigned char read_reg[1];
+
+ /* =============================================== */
+
+ /* Step 1 - Test register initialization */
+ dummyData = 0x00;
+ result = inv_serial_single_write(st,
+ YAS530_REGADDR_TEST1, dummyData);
+ if (result)
+ return result;
+ result =
+ inv_serial_single_write(st,
+ YAS530_REGADDR_TEST2, dummyData);
+ if (result)
+ return result;
+ /* Device ID read */
+ result = inv_serial_read(st,
+ YAS530_REGADDR_DEVICE_ID, 1, read_reg);
+
+ /*Step 2 Read the CAL register */
+ st->get_cal_data(st);
+
+ /*Obtain the [49:47] bits */
+ dck &= 0x07;
+
+ /*Step 3 : Storing the CONFIG with the CLK value */
+ dummyData = 0x00 | (dck << 2);
+ result = inv_serial_single_write(st,
+ YAS530_REGADDR_CONFIG, dummyData);
+ if (result)
+ return result;
+ /*Step 4 : Set Acquisition Interval Register */
+ dummyData = 0x00;
+ result = inv_serial_single_write(st,
+ YAS530_REGADDR_MEASURE_INTERVAL,
+ dummyData);
+ if (result)
+ return result;
+
+ /*Step 5 : Reset Coil */
+ dummyData = 0x00;
+ result = inv_serial_single_write(st,
+ YAS530_REGADDR_ACTUATE_INIT_COIL,
+ dummyData);
+ if (result)
+ return result;
+ /* Offset Measurement and Set */
+ result = measure_and_set_offset(st, st->offset);
+ if (result)
+ return result;
+ st->first_measure_after_reset = 1;
+ st->first_read_after_reset = 1;
+ st->reset_timer = 0;
+
+ yas_init_adap_filter(st);
+
+ return result;
+}
+
+static int inv_check_range(struct inv_compass_state *st, s16 x, s16 y1, s16 y2)
+{
+ int result = 0;
+
+ if (x == 0)
+ result |= 0x01;
+ if (x == st->overflow_bound)
+ result |= 0x02;
+ if (y1 == 0)
+ result |= 0x04;
+ if (y1 == st->overflow_bound)
+ result |= 0x08;
+ if (y2 == 0)
+ result |= 0x10;
+ if (y2 == st->overflow_bound)
+ result |= 0x20;
+
+ return result;
+}
+static int square(int data)
+{
+ return data * data;
+}
+
+static int
+adaptive_filter_filter(struct yas_adaptive_filter *adap_filter, int in)
+{
+ int avg, sum;
+ int i;
+
+ if (adap_filter->filter_len == 0)
+ return in;
+ if (adap_filter->num < adap_filter->filter_len) {
+ adap_filter->sequence[adap_filter->index++] = in / 100;
+ adap_filter->num++;
+ return in;
+ }
+ if (adap_filter->filter_len <= adap_filter->index)
+ adap_filter->index = 0;
+ adap_filter->sequence[adap_filter->index++] = in / 100;
+
+ avg = 0;
+ for (i = 0; i < adap_filter->filter_len; i++)
+ avg += adap_filter->sequence[i];
+ avg /= adap_filter->filter_len;
+
+ sum = 0;
+ for (i = 0; i < adap_filter->filter_len; i++)
+ sum += square(avg - adap_filter->sequence[i]);
+ sum /= adap_filter->filter_len;
+
+ if (sum <= adap_filter->filter_noise)
+ return avg * 100;
+
+ return ((in/100 - avg) * (sum - adap_filter->filter_noise) / sum + avg)
+ * 100;
+}
+
+static int
+thresh_filter_filter(struct yas_thresh_filter *thresh_filter, int in)
+{
+ if (in < thresh_filter->last - thresh_filter->threshold
+ || thresh_filter->last
+ + thresh_filter->threshold < in) {
+ thresh_filter->last = in;
+ return in;
+ } else {
+ return thresh_filter->last;
+ }
+}
+
+static void
+filter_filter(struct yas_filter *d, int *orig, int *filtered)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ filtered[i] = adaptive_filter_filter(&d->adap_filter[i],
+ orig[i]);
+ filtered[i] = thresh_filter_filter(&d->thresh_filter[i],
+ filtered[i]);
+ }
+}
+
+int yas53x_read(struct inv_compass_state *st, short rawfixed[3],
+ int *overunderflow)
+{
+ int result = 0;
+
+ int busy, i, ov;
+ short t, x, y1, y2;
+ s32 xyz[3], disturb[3];
+
+ result = measure_int(st, &busy, &t, &x, &y1, &y2);
+ if (result)
+ return result;
+ if (busy)
+ return -1;
+ coordinate_conversion(x, y1, y2, t, &xyz[0], &xyz[1], &xyz[2]);
+ filter_filter(&st->filter, xyz, xyz);
+ for (i = 0; i < 3; i++)
+ rawfixed[i] = (short)(xyz[i] / 100);
+
+ if (st->first_measure_after_reset) {
+ for (i = 0; i < 3; i++)
+ st->base_compass_data[i] = rawfixed[i];
+ st->first_measure_after_reset = 0;
+ }
+ ov = 0;
+ for (i = 0; i < 3; i++) {
+ disturb[i] = abs(st->base_compass_data[i] - rawfixed[i]);
+ if (disturb[i] > YAS_MAG_DISTURBURNCE_THRESHOLD)
+ ov = 1;
+ }
+ if (ov)
+ st->reset_timer += st->delay;
+ else
+ st->reset_timer = 0;
+
+ if (st->reset_timer > YAS_RESET_COIL_TIME_THRESHOLD)
+ *overunderflow = (1<<8);
+ else
+ *overunderflow = 0;
+ *overunderflow |= inv_check_range(st, x, y1, y2);
+
+ return 0;
+}
+
+/**
+ * yas53x_read_raw() - read raw method.
+ */
+static int yas53x_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask) {
+ struct inv_compass_state *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case 0:
+ if (!(iio_buffer_enabled(indio_dev)))
+ return -EINVAL;
+ if (chan->type == IIO_MAGN) {
+ *val = st->compass_data[chan->channel2 - IIO_MOD_X];
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_MAGN) {
+ *val = YAS530_SCALE;
+ return IIO_VAL_INT;
+ }
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * inv_compass_matrix_show() - show orientation matrix
+ */
+static ssize_t inv_compass_matrix_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ signed char *m;
+ struct inv_compass_state *st = iio_priv(indio_dev);
+ m = st->plat_data.orientation;
+ return sprintf(buf,
+ "%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+ m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
+}
+
+static ssize_t yas53x_rate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 data;
+ int error;
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_compass_state *st = iio_priv(indio_dev);
+
+ error = kstrtoint(buf, 10, &data);
+ if (error)
+ return error;
+ if (0 == data)
+ return -EINVAL;
+ /* transform rate to delay in ms */
+ data = MSEC_PER_SEC / data;
+
+ if (data > YAS530_MAX_DELAY)
+ data = YAS530_MAX_DELAY;
+ if (data < YAS530_MIN_DELAY)
+ data = YAS530_MIN_DELAY;
+ st->delay = data;
+
+ return count;
+}
+
+static ssize_t yas53x_rate_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_compass_state *st = iio_priv(indio_dev);
+ /* transform delay in ms to rate */
+ return sprintf(buf, "%d\n", (int)MSEC_PER_SEC / st->delay);
+}
+
+static ssize_t yas53x_overunderflow_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 data;
+ int error;
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_compass_state *st = iio_priv(indio_dev);
+
+ error = kstrtoint(buf, 10, &data);
+ if (error)
+ return error;
+ if (data)
+ return -EINVAL;
+ st->overunderflow = data;
+
+ return count;
+}
+
+static ssize_t yas53x_overunderflow_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct inv_compass_state *st = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", st->overunderflow);
+}
+
+void set_yas53x_enable(struct iio_dev *indio_dev, bool enable)
+{
+ struct inv_compass_state *st = iio_priv(indio_dev);
+
+ yas_init_adap_filter(st);
+ st->first_measure_after_reset = 1;
+ st->first_read_after_reset = 1;
+ schedule_delayed_work(&st->work, msecs_to_jiffies(st->delay));
+}
+
+static void yas53x_work_func(struct work_struct *work)
+{
+ struct inv_compass_state *st =
+ container_of((struct delayed_work *)work,
+ struct inv_compass_state, work);
+ struct iio_dev *indio_dev = iio_priv_to_dev(st);
+ u32 delay = msecs_to_jiffies(st->delay);
+
+ mutex_lock(&indio_dev->mlock);
+ if (!(iio_buffer_enabled(indio_dev)))
+ goto error_ret;
+
+ schedule_delayed_work(&st->work, delay);
+ inv_read_yas53x_fifo(indio_dev);
+ INV_I2C_INC_COMPASSIRQ();
+
+error_ret:
+ mutex_unlock(&indio_dev->mlock);
+}
+
+static const struct iio_chan_spec compass_channels[] = {
+ {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_X,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_YAS53X_SCAN_MAGN_X,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Y,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_YAS53X_SCAN_MAGN_Y,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ }, {
+ .type = IIO_MAGN,
+ .modified = 1,
+ .channel2 = IIO_MOD_Z,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,
+ .scan_index = INV_YAS53X_SCAN_MAGN_Z,
+ .scan_type = IIO_ST('s', 16, 16, 0)
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(INV_YAS53X_SCAN_TIMESTAMP)
+};
+
+static DEVICE_ATTR(compass_matrix, S_IRUGO, inv_compass_matrix_show, NULL);
+static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, yas53x_rate_show,
+ yas53x_rate_store);
+static DEVICE_ATTR(overunderflow, S_IRUGO | S_IWUSR,
+ yas53x_overunderflow_show, yas53x_overunderflow_store);
+
+static struct attribute *inv_yas53x_attributes[] = {
+ &dev_attr_compass_matrix.attr,
+ &dev_attr_sampling_frequency.attr,
+ &dev_attr_overunderflow.attr,
+ NULL,
+};
+static const struct attribute_group inv_attribute_group = {
+ .name = "yas53x",
+ .attrs = inv_yas53x_attributes
+};
+
+static const struct iio_info yas53x_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &yas53x_read_raw,
+ .attrs = &inv_attribute_group,
+};
+
+/*constant IIO attribute */
+/**
+ * inv_yas53x_probe() - probe function.
+ */
+static int inv_yas53x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct inv_compass_state *st;
+ struct iio_dev *indio_dev;
+ int result;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ result = -ENODEV;
+ goto out_no_free;
+ }
+ indio_dev = iio_allocate_device(sizeof(*st));
+ if (indio_dev == NULL) {
+ result = -ENOMEM;
+ goto out_no_free;
+ }
+ st = iio_priv(indio_dev);
+ st->client = client;
+ st->plat_data =
+ *(struct mpu_platform_data *)dev_get_platdata(&client->dev);
+ st->delay = 10;
+
+ i2c_set_clientdata(client, indio_dev);
+
+ if (!strcmp(id->name, "yas530")) {
+ st->read_data = yas530_read_data;
+ st->get_cal_data = get_cal_data_yas530;
+ st->overflow_bound = YAS_YAS530_DATA_OVERFLOW;
+ st->center = YAS_YAS530_DATA_CENTER;
+ st->filter.filter_thresh = YAS530_MAG_DEFAULT_FILTER_THRESH;
+ } else {
+ st->read_data = yas532_533_read_data;
+ st->get_cal_data = get_cal_data_yas532_533;
+ st->overflow_bound = YAS_YAS532_533_DATA_OVERFLOW;
+ st->center = YAS_YAS532_533_DATA_CENTER;
+ st->filter.filter_thresh = YAS532_MAG_DEFAULT_FILTER_THRESH;
+ }
+ st->upper_bound = st->center + (st->center >> 1);
+ st->lower_bound = (st->center >> 1);
+
+ result = yas53x_resume(st);
+ if (result)
+ goto out_free;
+
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->name = id->name;
+ indio_dev->channels = compass_channels;
+ indio_dev->num_channels = ARRAY_SIZE(compass_channels);
+ indio_dev->info = &yas53x_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->currentmode = INDIO_DIRECT_MODE;
+
+ result = inv_yas53x_configure_ring(indio_dev);
+ if (result)
+ goto out_free;
+ result = iio_buffer_register(indio_dev, indio_dev->channels,
+ indio_dev->num_channels);
+ if (result)
+ goto out_unreg_ring;
+ result = inv_yas53x_probe_trigger(indio_dev);
+ if (result)
+ goto out_remove_ring;
+
+ result = iio_device_register(indio_dev);
+ if (result)
+ goto out_remove_trigger;
+ INIT_DELAYED_WORK(&st->work, yas53x_work_func);
+ pr_info("%s: Probe name %s\n", __func__, id->name);
+
+ return 0;
+out_remove_trigger:
+ if (indio_dev->modes & INDIO_BUFFER_TRIGGERED)
+ inv_yas53x_remove_trigger(indio_dev);
+out_remove_ring:
+ iio_buffer_unregister(indio_dev);
+out_unreg_ring:
+ inv_yas53x_unconfigure_ring(indio_dev);
+out_free:
+ iio_free_device(indio_dev);
+out_no_free:
+ dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result);
+ return -EIO;
+}
+
+/**
+ * inv_yas53x_remove() - remove function.
+ */
+static int inv_yas53x_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+ struct inv_compass_state *st = iio_priv(indio_dev);
+ cancel_delayed_work_sync(&st->work);
+ iio_device_unregister(indio_dev);
+ inv_yas53x_remove_trigger(indio_dev);
+ iio_buffer_unregister(indio_dev);
+ inv_yas53x_unconfigure_ring(indio_dev);
+ iio_free_device(indio_dev);
+
+ dev_info(&client->adapter->dev, "inv_yas53x_iio module removed.\n");
+ return 0;
+}
+static const unsigned short normal_i2c[] = { I2C_CLIENT_END };
+/* device id table is used to identify what device can be
+ * supported by this driver
+ */
+static const struct i2c_device_id inv_yas53x_id[] = {
+ {"yas530", 0},
+ {"yas532", 0},
+ {"yas533", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, inv_yas53x_id);
+
+static struct i2c_driver inv_yas53x_driver = {
+ .class = I2C_CLASS_HWMON,
+ .probe = inv_yas53x_probe,
+ .remove = inv_yas53x_remove,
+ .id_table = inv_yas53x_id,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "inv_yas53x_iio",
+ },
+ .address_list = normal_i2c,
+};
+
+static int __init inv_yas53x_init(void)
+{
+ int result = i2c_add_driver(&inv_yas53x_driver);
+ if (result) {
+ pr_err("%s failed\n", __func__);
+ return result;
+ }
+ return 0;
+}
+
+static void __exit inv_yas53x_exit(void)
+{
+ i2c_del_driver(&inv_yas53x_driver);
+}
+
+module_init(inv_yas53x_init);
+module_exit(inv_yas53x_exit);
+
+MODULE_AUTHOR("Invensense Corporation");
+MODULE_DESCRIPTION("Invensense device driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("inv_yas53x_iio");
+/**
+ * @}
+ */
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_iio.h b/drivers/iio/magnetometer/inv_compass/inv_yas53x_iio.h
new file mode 100644
index 000000000000..92bf0af7ec7e
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_iio.h
@@ -0,0 +1,172 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_yas53x_iio.h
+ * @brief Struct definitions for the Invensense implementation
+ * of yas53x driver.
+ */
+
+#ifndef _INV_GYRO_H_
+#define _INV_GYRO_H_
+
+#include <linux/i2c.h>
+#include <linux/kfifo.h>
+#include <linux/miscdevice.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include <linux/mpu.h>
+
+#include "iio.h"
+#include "buffer.h"
+#include "trigger.h"
+
+#define YAS_MAG_MAX_FILTER_LEN 30
+struct yas_adaptive_filter {
+ int num;
+ int index;
+ int filter_len;
+ int filter_noise;
+ int sequence[YAS_MAG_MAX_FILTER_LEN];
+};
+
+struct yas_thresh_filter {
+ int threshold;
+ int last;
+};
+
+struct yas_filter {
+ int filter_len;
+ int filter_thresh;
+ int filter_noise[3];
+ struct yas_adaptive_filter adap_filter[3];
+ struct yas_thresh_filter thresh_filter[3];
+};
+
+/**
+ * struct inv_compass_state - Driver state variables.
+ * @plat_data: mpu platform data from board file.
+ * @client: i2c client handle.
+ * @chan_info: channel information.
+ * @trig: IIO trigger.
+ * @work: work structure.
+ * @delay: delay to schedule the next work.
+ * @overflow_bound: bound to determine overflow.
+ * @center: center of the measurement.
+ * @compass_data[3]: compass data store.
+ * @offset[3]: yas530 specific data.
+ * @base_compass_data[3]: first measure data after reset.
+ * @first_measure_after_reset:1: flag for first measurement after reset.
+ * @first_read_after_reset:1: flag for first read after reset.
+ * @reset_timer: timer to accumulate overflow conditions.
+ * @overunderflow:1: overflow and underflow flag.
+ * @filter: filter data structure.
+ * @read_data: function pointer of reading data from device.
+ * @get_cal_data: function pointer of reading cal data.
+ */
+struct inv_compass_state {
+ struct mpu_platform_data plat_data;
+ struct i2c_client *client;
+ struct iio_trigger *trig;
+ struct delayed_work work;
+ s16 delay;
+ s16 overflow_bound;
+ s16 upper_bound;
+ s16 lower_bound;
+ s16 center;
+ s16 compass_data[3];
+ s8 offset[3];
+ s16 base_compass_data[3];
+ u8 first_measure_after_reset:1;
+ u8 first_read_after_reset:1;
+ u8 overunderflow:1;
+ s32 reset_timer;
+ struct yas_filter filter;
+ int (*read_data)(struct inv_compass_state *st,
+ int *, u16 *, u16 *, u16 *, u16 *);
+ int (*get_cal_data)(struct inv_compass_state *);
+};
+
+/* scan element definition */
+enum inv_mpu_scan {
+ INV_YAS53X_SCAN_MAGN_X,
+ INV_YAS53X_SCAN_MAGN_Y,
+ INV_YAS53X_SCAN_MAGN_Z,
+ INV_YAS53X_SCAN_TIMESTAMP,
+};
+
+#define YAS530_REGADDR_DEVICE_ID 0x80
+#define YAS530_REGADDR_ACTUATE_INIT_COIL 0x81
+#define YAS530_REGADDR_MEASURE_COMMAND 0x82
+#define YAS530_REGADDR_CONFIG 0x83
+#define YAS530_REGADDR_MEASURE_INTERVAL 0x84
+#define YAS530_REGADDR_OFFSET_X 0x85
+#define YAS530_REGADDR_OFFSET_Y1 0x86
+#define YAS530_REGADDR_OFFSET_Y2 0x87
+#define YAS530_REGADDR_TEST1 0x88
+#define YAS530_REGADDR_TEST2 0x89
+#define YAS530_REGADDR_CAL 0x90
+#define YAS530_REGADDR_MEASURE_DATA 0xb0
+
+#define YAS530_MAX_DELAY 200
+#define YAS530_MIN_DELAY 5
+#define YAS530_SCALE 107374182L
+
+#define YAS_YAS530_VERSION_A 0 /* YAS530 (MS-3E Aver) */
+#define YAS_YAS530_VERSION_B 1 /* YAS530B (MS-3E Bver) */
+#define YAS_YAS530_VERSION_A_COEF 380
+#define YAS_YAS530_VERSION_B_COEF 550
+#define YAS_YAS530_DATA_CENTER 2048
+#define YAS_YAS530_DATA_OVERFLOW 4095
+#define YAS_YAS530_CAL_DATA_SIZE 16
+
+/*filter related defines */
+#define YAS_MAG_DEFAULT_FILTER_NOISE_X 144 /* sd: 1200 nT */
+#define YAS_MAG_DEFAULT_FILTER_NOISE_Y 144 /* sd: 1200 nT */
+#define YAS_MAG_DEFAULT_FILTER_NOISE_Z 144 /* sd: 1200 nT */
+#define YAS_MAG_DEFAULT_FILTER_LEN 20
+
+#define YAS530_MAG_DEFAULT_FILTER_THRESH 100
+#define YAS532_MAG_DEFAULT_FILTER_THRESH 300
+
+#define YAS_YAS532_533_VERSION_AB 0 /* YAS532_533AB (MS-3R/3F ABver) */
+#define YAS_YAS532_533_VERSION_AC 1 /* YAS532_533AC (MS-3R/3F ACver) */
+#define YAS_YAS532_533_VERSION_AB_COEF 1800
+#define YAS_YAS532_533_VERSION_AC_COEF 900
+#define YAS_YAS532_533_DATA_CENTER 4096
+#define YAS_YAS532_533_DATA_OVERFLOW 8190
+#define YAS_YAS532_533_CAL_DATA_SIZE 14
+
+#define YAS_MAG_DISTURBURNCE_THRESHOLD 1600
+#define YAS_RESET_COIL_TIME_THRESHOLD 3000
+
+#define INV_ERROR_COMPASS_DATA_OVERFLOW (-1)
+#define INV_ERROR_COMPASS_DATA_NOT_READY (-2)
+
+int inv_yas53x_configure_ring(struct iio_dev *indio_dev);
+void inv_yas53x_unconfigure_ring(struct iio_dev *indio_dev);
+int inv_yas53x_probe_trigger(struct iio_dev *indio_dev);
+void inv_yas53x_remove_trigger(struct iio_dev *indio_dev);
+void set_yas53x_enable(struct iio_dev *indio_dev, bool enable);
+void inv_read_yas53x_fifo(struct iio_dev *indio_dev);
+int yas53x_read(struct inv_compass_state *st, short rawfixed[3],
+ s32 *overunderflow);
+int yas53x_resume(struct inv_compass_state *st);
+
+#endif /* #ifndef _INV_GYRO_H_ */
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_ring.c b/drivers/iio/magnetometer/inv_compass/inv_yas53x_ring.c
new file mode 100644
index 000000000000..efcf49c68393
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_ring.c
@@ -0,0 +1,165 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_yas53x_ring.c
+ * @brief Invensense implementation for yas530/yas532/yas533.
+ * @details This driver currently works for the yas530/yas532/yas533.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+
+#include "iio.h"
+#include "kfifo_buf.h"
+#include "trigger_consumer.h"
+#include "sysfs.h"
+
+#include "inv_yas53x_iio.h"
+
+static s64 get_time_ns(void)
+{
+ struct timespec ts;
+ ktime_get_ts(&ts);
+
+ return timespec_to_ns(&ts);
+}
+
+static int put_scan_to_buf(struct iio_dev *indio_dev, unsigned char *d,
+ short *s, int scan_index)
+{
+ struct iio_buffer *ring = indio_dev->buffer;
+ int st;
+ int i, d_ind;
+
+ d_ind = 0;
+ for (i = 0; i < 3; i++) {
+ st = iio_scan_mask_query(indio_dev, ring, scan_index + i);
+ if (st) {
+ memcpy(&d[d_ind], &s[i], sizeof(s[i]));
+ d_ind += sizeof(s[i]);
+ }
+ }
+
+ return d_ind;
+}
+
+/**
+ * inv_read_yas53x_fifo() - Transfer data from FIFO to ring buffer.
+ */
+void inv_read_yas53x_fifo(struct iio_dev *indio_dev)
+{
+ struct inv_compass_state *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+ int d_ind;
+ s32 overunderflow;
+ s8 *tmp;
+ s64 tmp_buf[2];
+
+ if (!yas53x_read(st, st->compass_data, &overunderflow)) {
+ tmp = (u8 *)tmp_buf;
+ d_ind = put_scan_to_buf(indio_dev, tmp, st->compass_data,
+ INV_YAS53X_SCAN_MAGN_X);
+ if (ring->scan_timestamp)
+ tmp_buf[(d_ind + 7) / 8] = get_time_ns();
+ ring->access->store_to(indio_dev->buffer, tmp, 0);
+
+ if (overunderflow) {
+ yas53x_resume(st);
+ if (!st->overunderflow)
+ st->overunderflow = 1;
+ }
+ }
+}
+
+void inv_yas53x_unconfigure_ring(struct iio_dev *indio_dev)
+{
+ iio_kfifo_free(indio_dev->buffer);
+};
+
+static int inv_yas53x_postenable(struct iio_dev *indio_dev)
+{
+ struct inv_compass_state *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+
+ /* when all the outputs are disabled, even though buffer/enable is on,
+ do nothing */
+ if (!(iio_scan_mask_query(indio_dev, ring, INV_YAS53X_SCAN_MAGN_X) ||
+ iio_scan_mask_query(indio_dev, ring, INV_YAS53X_SCAN_MAGN_Y) ||
+ iio_scan_mask_query(indio_dev, ring, INV_YAS53X_SCAN_MAGN_Z)))
+ return 0;
+
+ set_yas53x_enable(indio_dev, true);
+ schedule_delayed_work(&st->work,
+ msecs_to_jiffies(st->delay));
+
+ return 0;
+}
+
+static int inv_yas53x_predisable(struct iio_dev *indio_dev)
+{
+ struct inv_compass_state *st = iio_priv(indio_dev);
+ struct iio_buffer *ring = indio_dev->buffer;
+
+ cancel_delayed_work_sync(&st->work);
+ clear_bit(INV_YAS53X_SCAN_MAGN_X, ring->scan_mask);
+ clear_bit(INV_YAS53X_SCAN_MAGN_Y, ring->scan_mask);
+ clear_bit(INV_YAS53X_SCAN_MAGN_Z, ring->scan_mask);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops inv_yas53x_ring_setup_ops = {
+ .preenable = &iio_sw_buffer_preenable,
+ .postenable = &inv_yas53x_postenable,
+ .predisable = &inv_yas53x_predisable,
+};
+
+int inv_yas53x_configure_ring(struct iio_dev *indio_dev)
+{
+ int ret = 0;
+ struct iio_buffer *ring;
+
+ ring = iio_kfifo_allocate(indio_dev);
+ if (!ring) {
+ ret = -ENOMEM;
+ return ret;
+ }
+ indio_dev->buffer = ring;
+ /* setup ring buffer */
+ ring->scan_timestamp = true;
+ indio_dev->setup_ops = &inv_yas53x_ring_setup_ops;
+
+ indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
+ return 0;
+}
+/**
+ * @}
+ */
+
diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_trigger.c b/drivers/iio/magnetometer/inv_compass/inv_yas53x_trigger.c
new file mode 100644
index 000000000000..a20ce2baa7e0
--- /dev/null
+++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_trigger.c
@@ -0,0 +1,91 @@
+/*
+* Copyright (C) 2012 Invensense, Inc.
+*
+* This software is licensed under the terms of the GNU General Public
+* License version 2, as published by the Free Software Foundation, and
+* may be copied, distributed, and modified under those terms.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+*/
+
+/**
+ * @addtogroup DRIVERS
+ * @brief Hardware drivers.
+ *
+ * @{
+ * @file inv_yas53x_trigger.c
+ * @brief Invensense implementation for yas530/yas532/yas533
+ * @details This driver currently works for the yas530/yas532/yas533
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+
+#include "iio.h"
+#include "sysfs.h"
+#include "trigger.h"
+
+#include "inv_yas53x_iio.h"
+
+static const struct iio_trigger_ops inv_yas53x_trigger_ops = {
+ .owner = THIS_MODULE,
+};
+
+int inv_yas53x_probe_trigger(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct inv_compass_state *st = iio_priv(indio_dev);
+
+ st->trig = iio_allocate_trigger("%s-dev%d",
+ indio_dev->name,
+ indio_dev->id);
+ if (st->trig == NULL) {
+ ret = -ENOMEM;
+ goto error_ret;
+ }
+ /* select default trigger */
+ st->trig->dev.parent = &st->client->dev;
+ st->trig->private_data = indio_dev;
+ st->trig->ops = &inv_yas53x_trigger_ops;
+ ret = iio_trigger_register(st->trig);
+
+ /* select default trigger */
+ indio_dev->trig = st->trig;
+ if (ret)
+ goto error_free_trig;
+
+ return 0;
+
+error_free_trig:
+ iio_free_trigger(st->trig);
+error_ret:
+ return ret;
+}
+
+void inv_yas53x_remove_trigger(struct iio_dev *indio_dev)
+{
+ struct inv_compass_state *st = iio_priv(indio_dev);
+
+ iio_trigger_unregister(st->trig);
+ iio_free_trigger(st->trig);
+}
+/**
+ * @}
+ */
+