summaryrefslogtreecommitdiff
path: root/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c
diff options
context:
space:
mode:
authorEvan Wilson <evan@oliodevices.com>2015-06-30 10:23:50 -0700
committerEvan Wilson <evan@oliodevices.com>2015-06-30 10:23:50 -0700
commit688863c9ef555051ce7cfbc3170e7ab65b832f03 (patch)
treef877ba825c58bf0c5b434760f69da8f55d0956a6 /drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c
parent5629b2f2364f4d376f903d1f144b6f0ca9f0238b (diff)
downloadolio-linux-3.10-688863c9ef555051ce7cfbc3170e7ab65b832f03.tar.xz
olio-linux-3.10-688863c9ef555051ce7cfbc3170e7ab65b832f03.zip
Initial ST patch
Change-Id: I66a587f5fcdf026ed472cb867fe9903051c2f78b
Diffstat (limited to 'drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c')
-rw-r--r--drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c448
1 files changed, 448 insertions, 0 deletions
diff --git a/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c
new file mode 100644
index 00000000000..8a7cc5176c2
--- /dev/null
+++ b/drivers/iio/imu/st_lsm6ds3/st_lsm6ds3_buffer.c
@@ -0,0 +1,448 @@
+/*
+ * STMicroelectronics lsm6ds3 buffer driver
+ *
+ * Copyright 2014 STMicroelectronics Inc.
+ *
+ * Denis Ciocca <denis.ciocca@st.com>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "st_lsm6ds3.h"
+
+#define ST_LSM6DS3_ENABLE_AXIS 0x07
+#define ST_LSM6DS3_FIFO_DIFF_L 0x3a
+#define ST_LSM6DS3_FIFO_DIFF_MASK 0x0fff
+#define ST_LSM6DS3_FIFO_DATA_OUT_L 0x3e
+#define ST_LSM6DS3_FIFO_DATA_OVR_2REGS 0x4000
+
+static void st_lsm6ds3_push_data_with_timestamp(struct lsm6ds3_data *cdata,
+ u8 index, u8 *data, int64_t timestamp)
+{
+ int i, n = 0;
+ struct iio_chan_spec const *chs = cdata->indio_dev[index]->channels;
+ uint16_t bfch, bfchs_out = 0, bfchs_in = 0;
+ struct lsm6ds3_sensor_data *sdata = iio_priv(cdata->indio_dev[index]);
+
+ for (i = 0; i < sdata->num_data_channels; i++) {
+ bfch = chs[i].scan_type.storagebits >> 3;
+
+ if (test_bit(i, cdata->indio_dev[index]->active_scan_mask)) {
+ memcpy(&sdata->buffer_data[bfchs_out],
+ &data[bfchs_in], bfch);
+ n++;
+ bfchs_out += bfch;
+ }
+
+ bfchs_in += bfch;
+ }
+
+ if (cdata->indio_dev[index]->scan_timestamp)
+ *(s64 *)((u8 *)sdata->buffer_data +
+ ALIGN(bfchs_out, sizeof(s64))) = timestamp;
+
+ iio_push_to_buffers(cdata->indio_dev[index], sdata->buffer_data);
+}
+
+static void st_lsm6ds3_parse_fifo_data(struct lsm6ds3_data *cdata, u16 read_len)
+{
+ u16 fifo_offset = 0;
+ u8 gyro_sip, accel_sip;
+#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT
+ u8 ext0_sip, ext1_sip;
+#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */
+
+ while (fifo_offset < read_len) {
+ gyro_sip = cdata->gyro_samples_in_pattern;
+ accel_sip = cdata->accel_samples_in_pattern;
+#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT
+ ext0_sip = cdata->ext0_samples_in_pattern;
+ ext1_sip = cdata->ext1_samples_in_pattern;
+#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */
+
+ do {
+ if (gyro_sip > 0) {
+ if (cdata->gyro_samples_to_discard > 0)
+ cdata->gyro_samples_to_discard--;
+ else {
+ st_lsm6ds3_push_data_with_timestamp(
+ cdata, ST_INDIO_DEV_GYRO,
+ &cdata->fifo_data[fifo_offset],
+ cdata->gyro_timestamp);
+ }
+
+ cdata->gyro_timestamp += cdata->gyro_deltatime;
+ fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE;
+ gyro_sip--;
+ }
+
+ if (accel_sip > 0) {
+ if (cdata->accel_samples_to_discard > 0)
+ cdata->accel_samples_to_discard--;
+ else {
+ st_lsm6ds3_push_data_with_timestamp(
+ cdata, ST_INDIO_DEV_ACCEL,
+ &cdata->fifo_data[fifo_offset],
+ cdata->accel_timestamp);
+ }
+
+ cdata->accel_timestamp +=
+ cdata->accel_deltatime;
+ fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE;
+ accel_sip--;
+ }
+
+#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT
+ if (ext0_sip > 0) {
+ if (cdata->ext_samples_to_discard[0] > 0)
+ cdata->ext_samples_to_discard[0]--;
+ else {
+ st_lsm6ds3_push_data_with_timestamp(
+ cdata, ST_INDIO_DEV_EXT0,
+ &cdata->fifo_data[fifo_offset],
+ cdata->ext0_timestamp);
+ }
+
+ cdata->ext0_timestamp += cdata->ext0_deltatime;
+ fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE;
+ ext0_sip--;
+ }
+
+ if (ext1_sip > 0) {
+ if (cdata->ext_samples_to_discard[1] > 0)
+ cdata->ext_samples_to_discard[1]--;
+ else {
+ st_lsm6ds3_push_data_with_timestamp(
+ cdata, ST_INDIO_DEV_EXT1,
+ &cdata->fifo_data[fifo_offset],
+ cdata->ext1_timestamp);
+ }
+
+ cdata->ext1_timestamp += cdata->ext1_deltatime;
+ fifo_offset += ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE;
+ ext1_sip--;
+ }
+
+ } while ((accel_sip > 0) || (gyro_sip > 0) ||
+ (ext0_sip > 0) || (ext1_sip > 0));
+#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */
+ } while ((accel_sip > 0) || (gyro_sip > 0));
+#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */
+ }
+
+ return;
+}
+
+void st_lsm6ds3_read_fifo(struct lsm6ds3_data *cdata, bool check_fifo_len)
+{
+ int err;
+#if (CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO > 0)
+ u16 data_remaining, data_to_read;
+#endif /* CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO */
+ u16 read_len = cdata->fifo_threshold, byte_in_pattern;
+
+ if (!cdata->fifo_data)
+ return;
+
+ if (check_fifo_len) {
+ err = cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DIFF_L,
+ 2, (u8 *)&read_len, true);
+ if (err < 0)
+ return;
+
+ if (read_len & ST_LSM6DS3_FIFO_DATA_OVR_2REGS) {
+ dev_err(cdata->dev,
+ "data fifo overrun, failed to read it.\n");
+ return;
+ }
+
+ read_len &= ST_LSM6DS3_FIFO_DIFF_MASK;
+ read_len *= ST_LSM6DS3_BYTE_FOR_CHANNEL;
+
+#ifdef CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT
+ byte_in_pattern = (cdata->accel_samples_in_pattern +
+ cdata->gyro_samples_in_pattern +
+ cdata->ext0_samples_in_pattern +
+ cdata->ext1_samples_in_pattern) *
+ ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE;
+#else /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */
+ byte_in_pattern = (cdata->accel_samples_in_pattern +
+ cdata->gyro_samples_in_pattern) *
+ ST_LSM6DS3_FIFO_ELEMENT_LEN_BYTE;
+#endif /* CONFIG_ST_LSM6DS3_IIO_MASTER_SUPPORT */
+
+ read_len = (read_len / byte_in_pattern) * byte_in_pattern;
+
+ if (read_len > cdata->fifo_threshold)
+ read_len = cdata->fifo_threshold;
+ }
+
+ if (read_len == 0)
+ return;
+
+#if (CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO == 0)
+ err = cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DATA_OUT_L,
+ read_len, cdata->fifo_data, true);
+ if (err < 0)
+ return;
+#else /* CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO */
+ data_remaining = read_len;
+
+ do {
+ if (data_remaining > CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO)
+ data_to_read = CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO;
+ else
+ data_to_read = data_remaining;
+
+ err = cdata->tf->read(cdata, ST_LSM6DS3_FIFO_DATA_OUT_L,
+ data_to_read, &cdata->fifo_data[read_len - data_remaining], true);
+ if (err < 0)
+ return;
+
+ data_remaining -= data_to_read;
+ } while (data_remaining > 0);
+#endif /* CONFIG_ST_LSM6DS3_IIO_LIMIT_FIFO */
+
+ st_lsm6ds3_parse_fifo_data(cdata, read_len);
+}
+
+static irqreturn_t st_lsm6ds3_step_counter_trigger_handler(int irq, void *p)
+{
+ int err;
+ struct timespec ts;
+ int64_t timestamp = 0;
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev);
+
+ if (!sdata->cdata->reset_steps) {
+ err = sdata->cdata->tf->read(sdata->cdata,
+ (u8)indio_dev->channels[0].address,
+ ST_LSM6DS3_BYTE_FOR_CHANNEL,
+ sdata->buffer_data, true);
+ if (err < 0)
+ goto st_lsm6ds3_step_counter_done;
+
+ timestamp = sdata->cdata->timestamp;
+ } else {
+ memset(sdata->buffer_data, 0, ST_LSM6DS3_BYTE_FOR_CHANNEL);
+ get_monotonic_boottime(&ts);
+ timestamp = timespec_to_ns(&ts);
+ sdata->cdata->reset_steps = false;
+ }
+
+ if (indio_dev->scan_timestamp)
+ *(s64 *)((u8 *)sdata->buffer_data +
+ ALIGN(ST_LSM6DS3_BYTE_FOR_CHANNEL,
+ sizeof(s64))) = timestamp;
+
+ iio_push_to_buffers(indio_dev, sdata->buffer_data);
+
+st_lsm6ds3_step_counter_done:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static inline irqreturn_t st_lsm6ds3_handler_empty(int irq, void *p)
+{
+ return IRQ_HANDLED;
+}
+
+int st_lsm6ds3_trig_set_state(struct iio_trigger *trig, bool state)
+{
+ int err;
+ struct lsm6ds3_sensor_data *sdata;
+
+ sdata = iio_priv(iio_trigger_get_drvdata(trig));
+
+ err = st_lsm6ds3_set_drdy_irq(sdata, state);
+
+ return err < 0 ? err : 0;
+}
+
+static int st_lsm6ds3_buffer_preenable(struct iio_dev *indio_dev)
+{
+ int err;
+ struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev);
+
+ err = st_lsm6ds3_set_enable(sdata, true);
+ if (err < 0)
+ return err;
+
+ err = st_lsm6ds3_reconfigure_fifo(sdata->cdata, true);
+ if (err < 0)
+ return err;
+
+ return iio_sw_buffer_preenable(indio_dev);
+}
+
+static int st_lsm6ds3_buffer_postenable(struct iio_dev *indio_dev)
+{
+ int err;
+ struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev);
+
+ if ((1 << sdata->sindex) & ST_LSM6DS3_USE_BUFFER) {
+ sdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
+ if (sdata->buffer_data == NULL)
+ return -ENOMEM;
+ }
+
+ if ((sdata->sindex == ST_INDIO_DEV_ACCEL) ||
+ (sdata->sindex == ST_INDIO_DEV_GYRO)) {
+ err = st_lsm6ds3_set_axis_enable(sdata,
+ (u8)indio_dev->active_scan_mask[0]);
+ if (err < 0)
+ goto free_buffer_data;
+ }
+
+ err = iio_triggered_buffer_postenable(indio_dev);
+ if (err < 0)
+ goto free_buffer_data;
+
+ if (sdata->sindex == ST_INDIO_DEV_STEP_COUNTER) {
+ iio_trigger_poll_chained(
+ sdata->cdata->trig[ST_INDIO_DEV_STEP_COUNTER], 0);
+ }
+
+ if (sdata->sindex == ST_INDIO_DEV_SIGN_MOTION)
+ sdata->cdata->sign_motion_event_ready = true;
+
+ return 0;
+
+free_buffer_data:
+ if ((1 << sdata->sindex) & ST_LSM6DS3_USE_BUFFER)
+ kfree(sdata->buffer_data);
+
+ return err;
+}
+
+static int st_lsm6ds3_buffer_predisable(struct iio_dev *indio_dev)
+{
+ int err;
+ struct lsm6ds3_sensor_data *sdata = iio_priv(indio_dev);
+
+ err = iio_triggered_buffer_predisable(indio_dev);
+ if (err < 0)
+ return err;
+
+ if ((sdata->sindex == ST_INDIO_DEV_ACCEL) ||
+ (sdata->sindex == ST_INDIO_DEV_GYRO)) {
+ err = st_lsm6ds3_set_axis_enable(sdata, ST_LSM6DS3_ENABLE_AXIS);
+ if (err < 0)
+ return err;
+ }
+
+ if (sdata->sindex == ST_INDIO_DEV_SIGN_MOTION)
+ sdata->cdata->sign_motion_event_ready = false;
+
+ err = st_lsm6ds3_set_enable(sdata, false);
+ if (err < 0)
+ return err;
+
+ err = st_lsm6ds3_reconfigure_fifo(sdata->cdata, true);
+ if (err < 0)
+ return err;
+
+ if ((1 << sdata->sindex) & ST_LSM6DS3_USE_BUFFER)
+ kfree(sdata->buffer_data);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops st_lsm6ds3_buffer_setup_ops = {
+ .preenable = &st_lsm6ds3_buffer_preenable,
+ .postenable = &st_lsm6ds3_buffer_postenable,
+ .predisable = &st_lsm6ds3_buffer_predisable,
+};
+
+int st_lsm6ds3_allocate_rings(struct lsm6ds3_data *cdata)
+{
+ int err;
+
+ err = iio_triggered_buffer_setup(cdata->indio_dev[ST_INDIO_DEV_ACCEL],
+ &st_lsm6ds3_handler_empty, NULL,
+ &st_lsm6ds3_buffer_setup_ops);
+ if (err < 0)
+ return err;
+
+ err = iio_triggered_buffer_setup(cdata->indio_dev[ST_INDIO_DEV_GYRO],
+ &st_lsm6ds3_handler_empty, NULL,
+ &st_lsm6ds3_buffer_setup_ops);
+ if (err < 0)
+ goto buffer_cleanup_accel;
+
+ err = iio_triggered_buffer_setup(
+ cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION],
+ &st_lsm6ds3_handler_empty, NULL,
+ &st_lsm6ds3_buffer_setup_ops);
+ if (err < 0)
+ goto buffer_cleanup_gyro;
+
+ err = iio_triggered_buffer_setup(
+ cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER],
+ NULL,
+ &st_lsm6ds3_step_counter_trigger_handler,
+ &st_lsm6ds3_buffer_setup_ops);
+ if (err < 0)
+ goto buffer_cleanup_sign_motion;
+
+ err = iio_triggered_buffer_setup(
+ cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR],
+ &st_lsm6ds3_handler_empty, NULL,
+ &st_lsm6ds3_buffer_setup_ops);
+ if (err < 0)
+ goto buffer_cleanup_step_counter;
+
+ err = iio_triggered_buffer_setup(
+ cdata->indio_dev[ST_INDIO_DEV_TILT],
+ &st_lsm6ds3_handler_empty, NULL,
+ &st_lsm6ds3_buffer_setup_ops);
+ if (err < 0)
+ goto buffer_cleanup_step_detector;
+
+ return 0;
+
+buffer_cleanup_step_detector:
+ iio_triggered_buffer_cleanup(
+ cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]);
+buffer_cleanup_step_counter:
+ iio_triggered_buffer_cleanup(
+ cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]);
+buffer_cleanup_sign_motion:
+ iio_triggered_buffer_cleanup(
+ cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]);
+buffer_cleanup_gyro:
+ iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_GYRO]);
+buffer_cleanup_accel:
+ iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_ACCEL]);
+ return err;
+}
+
+void st_lsm6ds3_deallocate_rings(struct lsm6ds3_data *cdata)
+{
+ iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_TILT]);
+ iio_triggered_buffer_cleanup(
+ cdata->indio_dev[ST_INDIO_DEV_STEP_DETECTOR]);
+ iio_triggered_buffer_cleanup(
+ cdata->indio_dev[ST_INDIO_DEV_STEP_COUNTER]);
+ iio_triggered_buffer_cleanup(
+ cdata->indio_dev[ST_INDIO_DEV_SIGN_MOTION]);
+ iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_ACCEL]);
+ iio_triggered_buffer_cleanup(cdata->indio_dev[ST_INDIO_DEV_GYRO]);
+}
+
+MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics lsm6ds3 buffer driver");
+MODULE_LICENSE("GPL v2");