summaryrefslogtreecommitdiff
path: root/drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c')
-rw-r--r--drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c969
1 files changed, 969 insertions, 0 deletions
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 00000000..6af420bb
--- /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");
+/**
+ * @}
+ */
+