From: Jonathan Cameron <[EMAIL PROTECTED]>

Add support for ST LIS3L02DQ accelerometer as found on the xbow imote2 sensor
board. Provides direct access via sysfs interfaces and a software ring buffer
using the chips datardy interrupt as a trigger. Motion detection and data ready
events are available on a chrdev as are ring buffer 50% and 100% full events.

Signed-off-by: Jonathan Cameron <[EMAIL PROTECTED]>
---
Patch depends on iio_core patch
drivers/industrialio/Kconfig                   |    1
drivers/industrialio/Makefile                  |    1
drivers/industrialio/accelerometer/Kconfig     |   19
drivers/industrialio/accelerometer/Makefile    |    5
drivers/industrialio/accelerometer/lis3l02dq.c | 1371 +++++++++++++++++++++++++
drivers/industrialio/accelerometer/lis3l02dq.h |  168 +++
6 files changed, 1565 insertions(+)

--- a/drivers/industrialio/Makefile     2008-07-23 16:49:28.000000000 +0100
+++ b/drivers/industrialio/Makefile     2008-07-23 16:54:42.000000000 +0100
@@ -7,3 +7,4 @@ obj-$(CONFIG_INDUSTRIALIO)      += industrial
 obj-$(CONFIG_INDUSTRIALIO_PTIMER_BOARDINFO) += industrialio_ptimer_board_info.o
 
 obj-y += adc/
+obj-y += accelerometer/
--- a/drivers/industrialio/Kconfig      2008-07-23 16:52:14.000000000 +0100
+++ b/drivers/industrialio/Kconfig      2008-07-23 15:44:45.000000000 +0100
@@ -16,6 +16,7 @@ config INDUSTRIALIO_PTIMER_BOARDINFO
        boolean
        default y
 
+source drivers/industrialio/accelerometer/Kconfig
 source drivers/industrialio/adc/Kconfig
 
 endif
--- a/drivers/industrialio/accelerometer/Kconfig        1970-01-01 
01:00:00.000000000 +0100
+++ b/drivers/industrialio/accelerometer/Kconfig        2008-07-23 
16:57:39.000000000 +0100
@@ -0,0 +1,19 @@
+#
+# Accelerometer drivers
+#
+
+config LIS3L02DQ
+       tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver"
+       help
+         Say yes here to build generic support for the ST microelectronics
+         accelerometer. You will also need to one or more of the bus specific
+         elements below. The driver supplies direct access via sysfs files
+         and a software ring buffer using a supplied datardy interrupt.
+
+config LIS3L02DQ_SPI
+       depends on LIS3L02DQ && SPI
+       tristate "SPI support"
+       help
+         Say yes here to build support for the ST LIS3L02DQ accelerometer via
+         an SPI bus.
+
--- a/drivers/industrialio/accelerometer/Makefile       1970-01-01 
01:00:00.000000000 +0100
+++ b/drivers/industrialio/accelerometer/Makefile       2008-07-14 
17:26:34.000000000 +0100
@@ -0,0 +1,5 @@
+#
+# Makefile for industrial I/O accelerometer drivers
+#
+
+obj-$(CONFIG_LIS3L02DQ_SPI)    += lis3l02dq.o
--- a/drivers/industrialio/accelerometer/lis3l02dq.h    1970-01-01 
01:00:00.000000000 +0100
+++ b/drivers/industrialio/accelerometer/lis3l02dq.h    2008-07-21 
17:29:56.000000000 +0100
@@ -0,0 +1,168 @@
+/*
+ * LISL02DQ.h -- support STMicroelectronics LISD02DQ
+ *               3d 2g Linear Accelerometers via SPI
+ *
+ * Copyright (c) 2007 Jonathan Cameron <[EMAIL PROTECTED]>
+ *
+ * Loosely based upon tle62x0.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef SPI_LIS3L02DQ_H_
+#define SPI_LIS3L02DQ_H_
+#define LIS3L02DQ_READ_REG(a) ((a) | 0x80)
+#define LIS3L02DQ_WRITE_REG(a) a
+
+/* Calibration parameters */
+#define LIS3L02DQ_REG_OFFSET_X_ADDRESS         0x16
+#define LIS3L02DQ_REG_OFFSET_Y_ADDRESS         0x17
+#define LIS3L02DQ_REG_OFFSET_Z_ADDRESS         0x18
+
+#define LIS3L02DQ_REG_GAIN_X_ADDRESS           0x19
+#define LIS3L02DQ_REG_GAIN_Y_ADDRESS           0x1A
+#define LIS3L02DQ_REG_GAIN_Z_ADDRESS           0x1B
+
+/* Control Register (1 of 2) */
+#define LIS3L02DQ_REG_CTRL_1_ADDRESS           0x20
+/* Power ctrl - either bit set corresponds to on*/
+#define LIS3L02DQ_REG_CTRL_1_PD_ON     0xC0
+
+/* Decimation Factor  */
+#define LIS3L02DQ_DEC_MASK                     0x30
+#define LIS3L02DQ_REG_CTRL_1_DF_128            0x00
+#define LIS3L02DQ_REG_CTRL_1_DF_64             0x10
+#define LIS3L02DQ_REG_CTRL_1_DF_32             0x20
+#define LIS3L02DQ_REG_CTRL_1_DF_8              (0x10 | 0x20)
+
+/* Self Test Enable */
+#define LIS3L02DQ_REG_CTRL_1_SELF_TEST_ON      0x08
+
+/* Axes enable ctrls */
+#define LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE     0x04
+#define LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE     0x02
+#define LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE     0x01
+
+/* Control Register (2 of 2) */
+#define LIS3L02DQ_REG_CTRL_2_ADDRESS           0x21
+
+/* Block Data Update only after MSB and LSB read */
+#define LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE      0x40
+
+/* Set to big endian output */
+#define LIS3L02DQ_REG_CTRL_2_BIG_ENDIAN                0x20
+
+/* Reboot memory content */
+#define LIS3L02DQ_REG_CTRL_2_REBOOT_MEMORY     0x10
+
+/* Interupt Enable - applies data ready to the RDY pad */
+#define LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT  0x08
+
+/* Enable Data Ready Generation - relationship with previous unclear in docs */
+#define LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION 0x04
+
+/* SPI 3 wire mode */
+#define LIS3L02DQ_REG_CTRL_2_THREE_WIRE_SPI_MODE       0x02
+
+/* Data alignment, default is 12 bit right justified
+ * - option for 16 bit left justified */
+#define LIS3L02DQ_REG_CTRL_2_DATA_ALIGNMENT_16_BIT_LEFT_JUSTIFIED      0x01
+
+/* Interupt related stuff */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS              0x23
+
+/* Switch from or combination fo conditions to and */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_BOOLEAN_AND          0x80
+
+/* Latch interupt request,
+ * if on ack must be given by reading the ack register */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC            0x40
+
+/* Z Interupt on High (above threshold)*/
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH     0x20
+/* Z Interupt on Low */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW      0x10
+/* Y Interupt on High */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH     0x08
+/* Y Interupt on Low */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW      0x04
+/* X Interupt on High */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH     0x02
+/* X Interupt on Low */
+#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_LOW 0x01
+
+/* Register that gives description of what caused interupt
+ * - latched if set in CFG_ADDRES */
+#define LIS3L02DQ_REG_WAKE_UP_SRC_ADDRESS              0x24
+/* top bit ignored */
+/* Interupt Active */
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_ACTIVATED  0x40
+/* Interupts that have been triggered */
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH     0x20
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW      0x10
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH     0x08
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW      0x04
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH     0x02
+#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW      0x01
+
+#define LIS3L02DQ_REG_WAKE_UP_ACK_ADDRESS              0x25
+
+/* Status register */
+#define LIS3L02DQ_REG_STATUS_ADDRESS                   0x27
+/* XYZ axis data overrun - first is all overrun? */
+#define LIS3L02DQ_REG_STATUS_XYZ_OVERRUN               0x80
+#define LIS3L02DQ_REG_STATUS_Z_OVERRUN                 0x40
+#define LIS3L02DQ_REG_STATUS_Y_OVERRUN                 0x20
+#define LIS3L02DQ_REG_STATUS_X_OVERRUN                 0x10
+/* XYZ new data available - first is all 3 available? */
+#define LIS3L02DQ_REG_STATUS_XYZ_NEW_DATA 0x08
+#define LIS3L02DQ_REG_STATUS_Z_NEW_DATA                        0x04
+#define LIS3L02DQ_REG_STATUS_Y_NEW_DATA                        0x02
+#define LIS3L02DQ_REG_STATUS_X_NEW_DATA                        0x01
+
+/* The accelerometer readings - low and high bytes.
+Form of high byte dependant on justification set in ctrl reg */
+#define LIS3L02DQ_REG_OUT_X_L_ADDRESS                  0x28
+#define LIS3L02DQ_REG_OUT_X_H_ADDRESS                  0x29
+#define LIS3L02DQ_REG_OUT_Y_L_ADDRESS                  0x2A
+#define LIS3L02DQ_REG_OUT_Y_H_ADDRESS                  0x2B
+#define LIS3L02DQ_REG_OUT_Z_L_ADDRESS                  0x2C
+#define LIS3L02DQ_REG_OUT_Z_H_ADDRESS                  0x2D
+
+/* Threshold values for all axes and both above and below thresholds
+ * - i.e. there is only one value */
+#define LIS3L02DQ_REG_THS_L_ADDRESS                    0x2E
+#define LIS3L02DQ_REG_THS_H_ADDRESS                    0x2F
+
+#define LIS3L02DQ_DEFAULT_CTRL1 (LIS3L02DQ_REG_CTRL_1_PD_ON          \
+                                | LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE \
+                                | LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE \
+                                | LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE \
+                                | LIS3L02DQ_REG_CTRL_1_DF_128)
+
+#define LIS3L02DQ_DEFAULT_CTRL2        0
+
+#define LIS3L02DQ_DIRECT_ONLY_MODE     -1
+#define LIS3L02DQ_DIRECT_MODE          0
+#define LIS3L02DQ_INTERRUPT_MODE       1
+
+#define LIS3L02DQ_BUFFER_LENGTH                100
+
+
+
+struct lis3l02dq_state {
+       struct spi_device               *us;
+       struct work_struct              work_data_rdy_ring;
+       struct work_struct              work_data_rdy_event;
+       struct iio_work_cont            work_cont_thresh;
+
+       /* Interrupt caught event - used as part of the datardy to ring bh
+          in ensuring interrupt line does not become locked high */
+       bool                            inter;
+       s64                             last_timestamp;
+       struct iio_dev                  *indio_dev;
+
+};
+#endif /* SPI_LIS3L02DQ_H_ */
--- a/drivers/industrialio/accelerometer/lis3l02dq.c    1970-01-01 
01:00:00.000000000 +0100
+++ b/drivers/industrialio/accelerometer/lis3l02dq.c    2008-07-23 
17:02:12.000000000 +0100
@@ -0,0 +1,1371 @@
+/*
+ * lis3l02dq.c support STMicroelectronics LISD02DQ
+ *             3d 2g Linear Accelerometers via SPI
+ *
+ * Copyright (c) 2007 Jonathan Cameron <[EMAIL PROTECTED]>
+ *
+ * Loosely based upon tle62x0.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * FIXME: MORE DOCS
+ *
+ * Settings:
+ * Latch on interrupt generation enabled as it simplifies when to reenable
+ * the interrupt.
+ * 16 bit left justified mode used.
+ *
+ * Not implemented as yet - 'channel' selection for scan. This will probably
+ * look quite similar to the mode selection code in max1363 but will affect 
only
+ * which channels are read and pushed to the ring.
+ * Complexities arise in preventing reads from ring for elements that are not
+ * there. Could move to an entirely 'scan' based interface, but at the cost
+ * of allowing direct reading (problem or not?).
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/rtc.h>
+#include <linux/industrialio.h>
+#include <linux/industrialio_sysfs.h>
+
+#include "lis3l02dq.h"
+
+/* Somewhat wasteful under arm type alignments - endianess issues
+ as well?*/
+union lis3l02dq_channel {
+       char data[2];
+       int16_t val;
+};
+
+struct lis3l02dq_datum {
+       union lis3l02dq_channel el[3];
+       s64 time;
+};
+
+/* Read all inputs in one spi message */
+static const char read_all_tx_array[] =
+{
+       0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_L_ADDRESS),
+       0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_H_ADDRESS),
+       0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_L_ADDRESS),
+       0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_H_ADDRESS),
+       0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_L_ADDRESS),
+       0, LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_H_ADDRESS),
+};
+
+static int lis3l02dq_read_all(struct lis3l02dq_state *st,
+                             unsigned char *rx_array)
+{
+       /* Sadly the device appears to require deselection between
+        * reading the different registers - hence the somewhat
+        * convoluted nature of this transfer
+        */
+       struct spi_transfer xfers[] = {
+               /* x low byte */
+               {
+                       .tx_buf = read_all_tx_array,
+                       .rx_buf = rx_array,
+                       .bits_per_word = 16,
+                       .len = 2,
+                       .cs_change = 1,
+               },
+               /* x high byte */
+               {
+                       .tx_buf = read_all_tx_array+2,
+                       .rx_buf = rx_array + 2,
+                       .bits_per_word = 16,
+                       .len = 2,
+                       .cs_change = 1,
+               },
+               /* y low byte */
+               {
+                       .tx_buf = read_all_tx_array+4,
+                       .rx_buf = rx_array + 4,
+                       .bits_per_word = 16,
+                       .len = 2,
+                       .cs_change = 1,
+               },
+               /* y high byte */
+               {
+                       .tx_buf = read_all_tx_array+6,
+                       .rx_buf = rx_array + 6,
+                       .bits_per_word = 16,
+                       .len = 2,
+                       .cs_change = 1,
+               },
+               /* z low byte */
+               {
+                       .tx_buf = read_all_tx_array+8,
+                       .rx_buf = rx_array + 8,
+                       .bits_per_word = 16,
+                       .len = 2,
+                       .cs_change = 1,
+               },
+               /* z high byte */
+               {
+                       .tx_buf = read_all_tx_array+10,
+                       .rx_buf = rx_array + 10,
+                       .bits_per_word = 16,
+                       .len = 2,
+                       .cs_change = 0,
+               },
+       };
+       struct spi_message msg;
+       int ret;
+       /* After these are trasmitted, the rx_buff should have
+        * values in alternate bytes
+        */
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfers[0], &msg);
+       spi_message_add_tail(&xfers[2], &msg);
+       spi_message_add_tail(&xfers[4], &msg);
+       spi_message_add_tail(&xfers[1], &msg);
+       spi_message_add_tail(&xfers[3], &msg);
+       spi_message_add_tail(&xfers[5], &msg);
+       ret = spi_sync(st->us, &msg);
+       if (ret) {
+               dev_err(&st->us->dev, "problem with get all accels");
+               goto err_ret;
+       }
+
+err_ret:
+       return ret;
+}
+
+static int lis3l02dq_spi_read_reg_int8_t(struct device *dev,
+                                        uint8_t reg_address,
+                                        int8_t *val)
+{
+       int ret;
+       struct spi_message msg;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       struct spi_transfer xfer = {
+               .tx_buf = NULL,
+               .rx_buf = NULL,
+               .bits_per_word = 16,
+               .len = 2,
+       };
+
+       xfer.tx_buf = kmalloc(4, GFP_KERNEL);
+       if (xfer.tx_buf == NULL) {
+               ret = -ENOMEM;
+               goto error_ret;
+       }
+
+       xfer.rx_buf = kmalloc(4, GFP_KERNEL);
+       if (xfer.rx_buf == NULL) {
+               ret = -ENOMEM;
+               goto error_free_tx;
+       }
+       ((unsigned char *)(xfer.tx_buf))[1] = LIS3L02DQ_READ_REG(reg_address);
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfer, &msg);
+       ret = spi_sync(st->us, &msg);
+       if (ret) {
+               dev_err(&st->us->dev, "problem with get x offset");
+               goto error_free_rx;
+       }
+       *val = ((unsigned char *)(xfer.rx_buf))[0];
+       kfree(xfer.rx_buf);
+       kfree(xfer.tx_buf);
+       return ret;
+error_free_rx:
+       kfree(xfer.rx_buf);
+error_free_tx:
+       kfree(xfer.tx_buf);
+error_ret:
+       return ret;
+}
+
+/*Returns into to allow full 0/255 range with error codes in negative range */
+static int lis3l02dq_spi_read_reg_uint8_t(struct device *dev,
+                                         uint8_t reg_address)
+{
+       uint8_t val;
+       int8_t ret;
+       struct spi_message msg;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       unsigned char *local_tx_buf;
+       unsigned char *local_rx_buf;
+       struct spi_transfer xfer = {
+               .tx_buf = NULL,
+               .rx_buf = NULL,
+               .bits_per_word = 16,
+               .len = 2,
+       };
+
+       local_rx_buf = kmalloc(4, GFP_KERNEL);
+       local_tx_buf = kmalloc(4, GFP_KERNEL);
+
+       local_tx_buf[1] = LIS3L02DQ_READ_REG(reg_address);
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfer, &msg);
+       ret = spi_sync(st->us, &msg);
+       kfree(local_tx_buf);
+       if (ret) {
+               dev_err(&st->us->dev, "problem with get x offset");
+               goto err_ret;
+       }
+       val = local_rx_buf[0];
+       kfree(local_rx_buf);
+
+       return val;
+err_ret:
+       kfree(local_rx_buf);
+
+       return ret;
+}
+
+static int lis3l02dq_spi_write_reg_int8_t(struct device *dev,
+                                         uint8_t reg_address,
+                                         int value)
+{
+       struct spi_message msg;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       unsigned char *local_tx_buf;
+       struct spi_transfer xfer = {
+               .tx_buf = NULL,
+               .rx_buf = NULL,
+               .bits_per_word = 16,
+               .len = 2,
+       };
+       int ret;
+
+       local_tx_buf = kmalloc(4, GFP_KERNEL);
+       if (local_tx_buf == NULL) {
+               ret = -ENOMEM;
+               goto error_ret;
+       }
+       xfer.tx_buf = local_tx_buf;
+       local_tx_buf[1] = LIS3L02DQ_WRITE_REG(reg_address);
+       local_tx_buf[0] = value;
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfer, &msg);
+       ret = spi_sync(st->us, &msg);
+       kfree(local_tx_buf);
+
+       if (ret) {
+               dev_err(&st->us->dev, "problem with writing 8 bit register");
+               goto error_ret;
+
+       }
+
+       return 0;
+error_ret:
+       return ret;
+}
+
+/* Relies on the MSB being one higher adress than the LSB */
+static int lis3l02dq_spi_write_reg_int16_t(struct device *dev,
+                                          uint8_t lower_reg_address,
+                                          int value)
+{
+       struct spi_message msg;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       unsigned char *local_tx_buf;
+       int ret;
+       union lis3l02dq_channel tval;
+       struct spi_transfer xfers [] = { {
+                       .tx_buf = NULL,
+                       .rx_buf = NULL,
+                       .bits_per_word = 16,
+                       .len = 2,
+               }, {
+                       .tx_buf = NULL,
+                       .rx_buf = NULL,
+                       .bits_per_word = 16,
+                       .len = 2,
+               },
+       };
+
+       tval.val = value;
+       local_tx_buf = kmalloc(4, GFP_KERNEL);
+       if (local_tx_buf == NULL) {
+               ret = -ENOMEM;
+               goto error_ret;
+       }
+       xfers[0].tx_buf = local_tx_buf;
+       xfers[1].tx_buf = local_tx_buf + 2;
+       local_tx_buf[1] = LIS3L02DQ_WRITE_REG(lower_reg_address);
+       local_tx_buf[0] = tval.data[0];
+       local_tx_buf[3] = LIS3L02DQ_WRITE_REG(lower_reg_address+1);
+       local_tx_buf[2] = tval.data[1];
+
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfers[0], &msg);
+       spi_message_add_tail(&xfers[1], &msg);
+       ret = spi_sync(st->us, &msg);
+       kfree(local_tx_buf);
+       if (ret) {
+               dev_err(&st->us->dev, "problem when writing 16 bit register");
+               return ret;
+       }
+
+       return 0;
+error_ret:
+       return ret;
+}
+
+static int lis3l02dq_spi_read_reg_int16_t(struct device *dev,
+                                         uint8_t lower_reg_address,
+                                         int16_t *val)
+{
+       struct spi_message msg;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       unsigned char *local_tx_buf;
+       unsigned char *local_rx_buf;
+       int ret;
+       /* slight abuse, but same form */
+       union lis3l02dq_channel tval;
+       struct spi_transfer xfers [] = { {
+                       .tx_buf = NULL,
+                       .rx_buf = NULL,
+                       .bits_per_word = 16,
+                       .len = 2,
+               }, {
+                       .tx_buf = NULL,
+                       .rx_buf = NULL,
+                       .bits_per_word = 16,
+                       .len = 2,
+               },
+       };
+       local_tx_buf = kmalloc(4, GFP_KERNEL);
+       if (local_tx_buf == NULL) {
+               ret = -ENOMEM;
+               goto error_ret;
+       }
+       local_rx_buf = kmalloc(4, GFP_KERNEL);
+       if (local_rx_buf == NULL) {
+               ret = -ENOMEM;
+               goto error_free_tx_buf;
+       }
+       xfers[0].tx_buf = local_tx_buf;
+       xfers[0].rx_buf = local_rx_buf;
+       xfers[1].tx_buf = local_tx_buf + 2;
+       xfers[1].rx_buf = local_rx_buf + 2;
+       local_tx_buf[0] = 0;
+       local_tx_buf[1] = LIS3L02DQ_READ_REG(lower_reg_address);
+       local_tx_buf[2] = 0;
+       local_tx_buf[3] = LIS3L02DQ_READ_REG(lower_reg_address+1);
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfers[0], &msg);
+       spi_message_add_tail(&xfers[1], &msg);
+       ret = spi_sync(st->us, &msg);
+       if (ret) {
+               dev_err(&st->us->dev, "problem when reading 16 bit register");
+               goto error_free_rx_buf;
+       }
+       /* FIXME - endianness problem? */
+       tval.data[0] = local_rx_buf[0];
+       tval.data[1] = local_rx_buf[2];
+       kfree(local_rx_buf);
+       kfree(local_tx_buf);
+       *val = tval.val;
+       return 0;
+error_free_rx_buf:
+       kfree(local_rx_buf);
+error_free_tx_buf:
+       kfree(local_tx_buf);
+error_ret:
+       return ret;
+}
+
+
+
+static ssize_t lis3l02dq_read_signed(struct device *dev,
+                                    struct device_attribute *attr,
+                                    char *buf)
+{
+       int len, ret;
+       int8_t val;
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+
+       ret = lis3l02dq_spi_read_reg_int8_t(dev, this_attr->address, &val);
+       if (ret < 0)
+               goto err_ret;
+       len = sprintf(buf, "%d\n", val);
+
+       return len;
+
+err_ret:
+       return ret;
+}
+
+static ssize_t lis3l02dq_read_unsigned(struct device *dev,
+                                      struct device_attribute *attr,
+                                      char *buf)
+{
+       int val, len;
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+
+       val = lis3l02dq_spi_read_reg_uint8_t(dev, this_attr->address);
+       if (val < 0) {
+               dev_err(dev, "problem reading an unsigned 8 bit value");
+               goto err_ret;
+       }
+
+       len = sprintf(buf, "%d\n", val);
+       return len;
+err_ret:
+       return val;
+}
+/* Used for offsets etc so no need for lock. */
+static ssize_t lis3l02dq_write_signed(struct device *dev,
+                                     struct device_attribute *attr,
+                                     const char *buf,
+                                     size_t len)
+{
+       long val;
+       int ret;
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+
+       ret = strict_strtol(buf, 10, &val);
+       if (ret)
+               goto err_ret;
+       ret = lis3l02dq_spi_write_reg_int8_t(dev, this_attr->address, val);
+       if (ret)
+               goto err_ret;
+
+       return len;
+
+err_ret:
+       return ret;
+}
+/* Used for gains etc so no need for lock. */
+static ssize_t lis3l02dq_write_unsigned(struct device *dev,
+                                       struct device_attribute *attr,
+                                       const char *buf,
+                                       size_t len)
+{
+       int ret;
+       ulong val;
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+
+       ret = strict_strtoul(buf, 10, &val);
+       if (ret)
+               goto err_ret;
+
+       ret = lis3l02dq_spi_write_reg_int8_t(dev, this_attr->address, val);
+       if (ret)
+               goto err_ret;
+
+       return len;
+
+err_ret:
+       return ret;
+}
+
+static ssize_t lis3l02dq_read_16bit_signed(struct device *dev,
+                                          struct device_attribute *attr,
+                                          char *buf)
+{
+       int len, ret;
+       int16_t val;
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+
+       ret = lis3l02dq_spi_read_reg_int16_t(dev, this_attr->address, &val);
+       if (ret < 0) {
+               dev_err(dev, "problem reading a signed 16 bit value from chip");
+               return ret;
+       }
+       len = sprintf(buf, "%d\n", val);
+
+       return len;
+}
+
+/* As the ring buffer contents are device dependent this functionality
+ * must remain part of the driver and not the ring buffer subsystem */
+static ssize_t
+lis3l02dq_read_accel_from_ring(struct iio_sw_ring_buffer *ring,
+                              int element, char *buf)
+{
+       int ret, len;
+       struct lis3l02dq_datum data;
+
+       ret = iio_read_last_from_sw_ring(ring, (char *)(&data));
+       if (ret)
+               return ret;
+       len = sprintf(buf, "ring %d\n", data.el[element].val);
+
+       return len;
+}
+/* Prevents mode switching when running */
+static ssize_t lis3l02dq_read_accel(struct device *dev,
+                                   struct device_attribute *attr,
+                                   char *buf)
+{
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       ssize_t returnval;
+       int element;
+
+       mutex_lock(&indio_dev->mlock);
+       if (indio_dev->currentmode == INDIO_RING_DATA_RDY) {
+               switch (this_attr->address) {
+               case LIS3L02DQ_REG_OUT_X_L_ADDRESS:
+                       element = 0;
+                       break;
+               case LIS3L02DQ_REG_OUT_Y_L_ADDRESS:
+                       element = 1;
+                       break;
+               case LIS3L02DQ_REG_OUT_Z_L_ADDRESS:
+                       element = 2;
+                       break;
+               default:
+                       returnval =  -EINVAL;
+                       goto error_ret;
+               }
+               returnval = lis3l02dq_read_accel_from_ring(indio_dev->ring,
+                                                          element, buf);
+       } else
+               returnval =  lis3l02dq_read_16bit_signed(dev, attr, buf);
+error_ret:
+       mutex_unlock(&indio_dev->mlock);
+       return returnval;
+}
+
+/* For this device this is only relevant to the threshold for interrupt
+ * generation */
+static ssize_t lis3l02dq_write_16bit_signed(struct device *dev,
+                                           struct device_attribute *attr,
+                                           const char *buf,
+                                           size_t len)
+{
+       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       int ret;
+       long val;
+
+       ret = strict_strtol(buf, 10, &val);
+       if (ret)
+               return ret;
+       mutex_lock(&indio_dev->mlock);
+       ret = lis3l02dq_spi_write_reg_int16_t(dev, this_attr->address, val);
+       mutex_unlock(&indio_dev->mlock);
+       if (ret)
+               return ret;
+
+       return len;
+}
+
+static ssize_t lis3l02dq_read_av_freq(struct device *dev,
+                                     struct device_attribute *attr,
+                                     char *buf)
+{
+       return sprintf(buf, "280 560 1120 4480\n");
+}
+
+static ssize_t lis3l02dq_read_frequency(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       int ret, len;
+       int8_t t;
+       ret = lis3l02dq_spi_read_reg_int8_t(dev,
+                                           LIS3L02DQ_REG_CTRL_1_ADDRESS,
+                                           &t);
+       if (ret)
+               goto error_ret;
+       t &= LIS3L02DQ_DEC_MASK;
+       if (t == LIS3L02DQ_REG_CTRL_1_DF_128)
+               len = sprintf(buf, "280");
+       else if (t == LIS3L02DQ_REG_CTRL_1_DF_64)
+               len = sprintf(buf, "560");
+       else if (t == LIS3L02DQ_REG_CTRL_1_DF_32)
+               len = sprintf(buf, "1120");
+       else
+               len = sprintf(buf, "4480");
+       len += sprintf(buf+len, "\n");
+
+       return len;
+
+error_ret:
+       return ret;
+}
+
+static ssize_t lis3l02dq_write_frequency(struct device *dev,
+                                        struct device_attribute *attr,
+                                        const char *buf,
+                                        size_t len)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       long val;
+       int ret;
+       int8_t t;
+
+       ret = strict_strtol(buf, 10, &val);
+       if (ret)
+               return ret;
+
+       mutex_lock(&indio_dev->mlock);
+       ret = lis3l02dq_spi_read_reg_int8_t(dev,
+                                           LIS3L02DQ_REG_CTRL_1_ADDRESS,
+                                           &t);
+       if (ret)
+               goto error_ret;
+       /* Wipe the bits clean */
+       t &= ~LIS3L02DQ_DEC_MASK;
+       switch (val) {
+       case 280:
+               t |= LIS3L02DQ_REG_CTRL_1_DF_128;
+               break;
+       case 560:
+               t |= LIS3L02DQ_REG_CTRL_1_DF_64;
+               break;
+       case 1120:
+               t |= LIS3L02DQ_REG_CTRL_1_DF_32;
+               break;
+       case 4480:
+               t |= LIS3L02DQ_REG_CTRL_1_DF_8;
+               break;
+       default:
+               ret = -EINVAL;
+               goto error_ret;
+       };
+
+       ret = lis3l02dq_spi_write_reg_int8_t(dev,
+                                            LIS3L02DQ_REG_CTRL_1_ADDRESS,
+                                            t);
+
+       if (ret)
+               goto error_ret;
+       mutex_unlock(&indio_dev->mlock);
+       return len;
+
+error_ret:
+       mutex_unlock(&indio_dev->mlock);
+       return ret;
+
+}
+
+static int lis3l02dq_initial_setup(struct lis3l02dq_state *st)
+{
+       int ret, val;
+
+       st->us->mode = SPI_MODE_3;
+       spi_setup(st->us);
+       /* Write suitable defaults to ctrl1 */
+       ret = lis3l02dq_spi_write_reg_int8_t(&st->us->dev,
+                                            LIS3L02DQ_REG_CTRL_1_ADDRESS,
+                                            LIS3L02DQ_DEFAULT_CTRL1);
+       if (ret) {
+               dev_err(&st->us->dev, "problem with setup control register 1");
+               goto err_ret;
+       }
+       ret = lis3l02dq_spi_write_reg_int8_t(&st->us->dev,
+                                            LIS3L02DQ_REG_CTRL_2_ADDRESS,
+                                            LIS3L02DQ_DEFAULT_CTRL2);
+       if (ret) {
+               dev_err(&st->us->dev, "problem with setup control register 2");
+               goto err_ret;
+       }
+       val = LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC;
+       ret = lis3l02dq_spi_write_reg_int8_t(&st->us->dev,
+                                            LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS,
+                                            val);
+       if (ret)
+               dev_err(&st->us->dev, "problem with interrupt cfg register");
+
+err_ret:
+
+       return ret;
+}
+
+
+
+/* These are all a case of reading / writing directly to the chip */
+
+static IIO_DEV_ATTR_ACCEL_X_OFFSET(S_IWUSR | S_IRUGO,
+                                  lis3l02dq_read_signed,
+                                  lis3l02dq_write_signed,
+                                  LIS3L02DQ_REG_OFFSET_X_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_Y_OFFSET(S_IWUSR | S_IRUGO,
+                                  lis3l02dq_read_signed,
+                                  lis3l02dq_write_signed,
+                                  LIS3L02DQ_REG_OFFSET_Y_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_Z_OFFSET(S_IWUSR | S_IRUGO,
+                                  lis3l02dq_read_signed,
+                                  lis3l02dq_write_signed,
+                                  LIS3L02DQ_REG_OFFSET_Z_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_X_GAIN(S_IWUSR | S_IRUGO,
+                                lis3l02dq_read_unsigned,
+                                lis3l02dq_write_unsigned,
+                                LIS3L02DQ_REG_GAIN_X_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_Y_GAIN(S_IWUSR | S_IRUGO,
+                                lis3l02dq_read_unsigned,
+                                lis3l02dq_write_unsigned,
+                                LIS3L02DQ_REG_GAIN_Y_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_Z_GAIN(S_IWUSR | S_IRUGO,
+                                lis3l02dq_read_unsigned,
+                                lis3l02dq_write_unsigned,
+                                LIS3L02DQ_REG_GAIN_Z_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_THRESH(S_IWUSR | S_IRUGO,
+                                lis3l02dq_read_16bit_signed,
+                                lis3l02dq_write_16bit_signed,
+                                LIS3L02DQ_REG_THS_L_ADDRESS);
+
+/* Obviously the reading method for these will change depending on whether
+   ring buffer capture is in use. Allow specification here of alternate
+   function? Or take the approach used above of making this a driver issue
+   rather than part of industrialio */
+static IIO_DEV_ATTR_ACCEL_X(lis3l02dq_read_accel,
+                           LIS3L02DQ_REG_OUT_X_L_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_Y(lis3l02dq_read_accel,
+                           LIS3L02DQ_REG_OUT_Y_L_ADDRESS);
+
+static IIO_DEV_ATTR_ACCEL_Z(lis3l02dq_read_accel,
+                           LIS3L02DQ_REG_OUT_Z_L_ADDRESS);
+
+static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO,
+                             lis3l02dq_read_frequency,
+                             lis3l02dq_write_frequency);
+
+static IIO_DEV_ATTR_AVAIL_SAMP_FREQ(lis3l02dq_read_av_freq);
+
+static ssize_t
+lis3l02dq_read_interrupt_config(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf)
+{
+       int len, ret, set;
+       int8_t val;
+       struct iio_event_attr *this_attr = to_iio_event_attr(attr);
+
+       ret = lis3l02dq_spi_read_reg_int8_t(dev,
+                                           LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS,
+                                           &val);
+       if (ret < 0)
+               return ret;
+       set = (val & this_attr->mask) ? 1 : 0;
+       len = sprintf(buf, "%d\n", set);
+
+       return len;
+}
+
+static ssize_t lis3l02dq_write_interrupt_config(struct device *dev,
+                                               struct device_attribute *attr,
+                                               const char *buf,
+                                               size_t len)
+{
+       struct iio_event_attr *this_attr = to_iio_event_attr(attr);
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+               int ret, currentlyset, addr, changed = 0;
+       int8_t valold, controlold;
+       bool val;
+
+       val = (buf[0] == '0') ? 0 : 1;
+
+       mutex_lock(&indio_dev->mlock);
+       /* read current value */
+       ret = lis3l02dq_spi_read_reg_int8_t(dev,
+                                           LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS,
+                                           &valold);
+       if (ret)
+               goto error_mutex_unlock;
+
+       /* read current control */
+       ret = lis3l02dq_spi_read_reg_int8_t(dev,
+                                           LIS3L02DQ_REG_CTRL_2_ADDRESS,
+                                           &controlold);
+       if (ret)
+               goto error_mutex_unlock;
+       currentlyset = valold & this_attr->mask ? 1 : 0;
+       if (val == false && currentlyset) {
+               valold &= ~this_attr->mask;
+               changed = 1;
+               ret = iio_remove_event_from_list(this_attr->listel);
+               if (ret)
+                       goto error_mutex_unlock;
+       } else if (val == true && !currentlyset) {
+               changed = 1;
+               valold |= this_attr->mask;
+               /* move to a line spec rather than this? */
+               ret = iio_add_event_to_list(&indio_dev->interrupts[0]->ev_list,
+                                           this_attr->listel);
+               if (ret)
+                       goto error_mutex_unlock;
+       }
+
+       if (changed) {
+               addr = LIS3L02DQ_REG_WAKE_UP_CFG_ADDRESS;
+               ret = lis3l02dq_spi_write_reg_int8_t(dev, addr, valold);
+               if (ret)
+                       goto error_mutex_unlock;
+               /* This always enables the interrupt, even if we've remove the
+                * last thing using it. For this device we can use the reference
+                * count on the handler to tell us if anyone
+                * wants the interrupt */
+               controlold = this_attr->listel->refcount ?
+                       (controlold | LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT):
+                       (controlold & ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT);
+               addr = LIS3L02DQ_REG_CTRL_2_ADDRESS;
+               ret = lis3l02dq_spi_write_reg_int8_t(dev, addr, controlold);
+               if (ret)
+                       goto error_mutex_unlock;
+       }
+       mutex_unlock(&indio_dev->mlock);
+
+       return len;
+
+error_mutex_unlock:
+       mutex_unlock(&indio_dev->mlock);
+       return ret;
+}
+
+static ssize_t lis3l02dq_read_data_ready_config(struct device *dev,
+                                               struct device_attribute *attr,
+                                               char *buf)
+{
+       int ret, set, len;
+       int8_t val;
+
+       ret = lis3l02dq_spi_read_reg_int8_t(dev,
+                                           LIS3L02DQ_REG_CTRL_2_ADDRESS,
+                                           &val);
+       if (ret < 0)
+               return ret;
+       set = (val & LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION) ? 1 : 0;
+       len = sprintf(buf, "%d\n", set);
+
+       return len;
+}
+
+/* Caller responsible for locking as necessary. */
+static int __lis3l02dq_write_data_ready_config(struct device *dev,
+                                              struct iio_event_handler_list
+                                              *list,
+                                              long state)
+{
+       int ret;
+       int8_t valold, addr;
+       bool currentlyset, changed = 0;
+
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+
+       ret = lis3l02dq_spi_read_reg_int8_t(dev,
+                                           LIS3L02DQ_REG_CTRL_2_ADDRESS,
+                                           &valold);
+       if (ret)
+               goto error_ret;
+
+       currentlyset
+               = valold & LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION
+               ? 1 : 0;
+       /* if set, disable requested and ring buffer not in use (FIXME) */
+       if (state == 0 && currentlyset) {
+               valold &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION;
+               ret = iio_remove_event_from_list(list);
+               if (ret)
+                       goto error_ret;
+               changed = true;
+       } else if (state != 0 && !currentlyset) {
+               /* if not set, enable requested */
+               valold |= LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION;
+               ret = iio_add_event_to_list(&indio_dev->interrupts[0]->ev_list,
+                                           list);
+               if (ret)
+                       goto error_ret;
+               changed = true;
+       }
+       if (changed) {
+               addr = LIS3L02DQ_REG_CTRL_2_ADDRESS;
+               ret = lis3l02dq_spi_write_reg_int8_t(dev, addr, valold);
+               if (ret)
+                       goto error_ret;
+       }
+
+       return 0;
+error_ret:
+       return ret;
+}
+
+/* Does nothing if the ring is in use */
+static ssize_t lis3l02dq_write_data_ready_config(struct device *dev,
+                                                struct device_attribute *attr,
+                                                const char *buf,
+                                                size_t len)
+{
+       int ret;
+       long val;
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct iio_event_attr *this_attr = to_iio_event_attr(attr);
+
+       mutex_lock(&indio_dev->mlock);
+       if (indio_dev->currentmode ==  INDIO_RING_DATA_RDY)
+               return len;
+
+       /* fixme not the simplest method out!*/
+       ret = strict_strtol(buf, 10, &val);
+       if (ret)
+               goto error_ret;
+
+       ret = __lis3l02dq_write_data_ready_config(dev, this_attr->listel, val);
+
+       if (ret)
+               goto error_ret;
+       mutex_unlock(&indio_dev->mlock);
+
+       return len;
+error_ret:
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret;
+}
+
+/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device
+ * specific to be rolled into the core.
+ */
+static void lis3l02dq_data_rdy_bh_to_ring(struct work_struct *work_s)
+{
+       struct lis3l02dq_state *st
+               = container_of(work_s, struct lis3l02dq_state,
+                              work_data_rdy_ring);
+       unsigned char *rx_array;
+       int i, j;
+       struct lis3l02dq_datum ring_data;
+
+       rx_array = kmalloc(12, GFP_KERNEL);
+       if (rx_array == NULL) {
+               dev_err(&st->us->dev, "memory alloc failed in ring bh");
+               return;
+       }
+       st->inter = 0;
+
+       if (lis3l02dq_read_all(st, rx_array) >= 0) {
+               for (i = 0; i < 3; i++)
+                       for (j = 0; j < 2; j++)
+                               ring_data.el[i].data[j] = rx_array[i*4+j*2];
+               ring_data.time = st->last_timestamp;
+               iio_store_to_sw_ring(&st->indio_dev->ring[0],
+                                    (char *)(&ring_data),
+                                    st->last_timestamp);
+       }
+       /* so the data should now be in the rx array */
+try_again:
+       while (gpio_get_value(irq_to_gpio(st->us->irq)))
+               if (lis3l02dq_read_all(st, rx_array) >= 0) {
+                       for (i = 0; i < 3; i++)
+                               for (j = 0; j < 2; j++)
+                                       ring_data.el[i].data[j]
+                                               = rx_array[i*4+j*2];
+                       ring_data.time = 0;
+                       /* Passing a negated time stamp to indicate that
+                        * we don't actually know when this occured! */
+                       iio_store_to_sw_ring(&st->indio_dev->ring[0],
+                                            (char *)(&ring_data),
+                                            0);
+               }
+       /* push data into ring buffer before trying renable */
+       enable_irq(st->us->irq);
+       if (gpio_get_value(irq_to_gpio(st->us->irq)))
+               if (st->inter == 0) {
+                       disable_irq_nosync(st->us->irq);
+                       goto try_again;
+               }
+       kfree(rx_array);
+
+       return;
+}
+
+static void lis3l02dq_data_rdy_bh_to_event(struct work_struct *work_s)
+{
+       struct lis3l02dq_state *st
+               = container_of(work_s, struct lis3l02dq_state,
+                              work_data_rdy_event);
+
+       /* send an event up to user space */
+       iio_put_event(st->indio_dev, 0, IIO_EVENT_CODE_DATA_RDY,
+                     st->last_timestamp);
+       enable_irq(st->us->irq);
+
+       return;
+}
+
+static int lis3l02dq_data_rdy_event_th(struct iio_dev *dev_info,
+                                int index,
+                                s64 timestamp,
+                                int no_test)
+{
+       struct lis3l02dq_state *st = dev_info->dev_data;
+
+       st->last_timestamp = timestamp;
+       schedule_work(&st->work_data_rdy_event);
+
+       return IRQ_HANDLED;
+}
+
+static int lis3l02dq_data_rdy_ring_th(struct iio_dev *dev_info,
+                                int index,
+                                s64 timestamp,
+                                int no_test)
+{
+       struct lis3l02dq_state *st = dev_info->dev_data;
+
+       st->last_timestamp = timestamp;
+       schedule_work(&st->work_data_rdy_ring);
+       st->inter = 1;
+
+       return IRQ_HANDLED;
+}
+
+static int lis3l02dq_thresh_handler_th(struct iio_dev *dev_info,
+                                      int index,
+                                      s64 timestamp,
+                                      int no_test)
+{
+       struct lis3l02dq_state *st = dev_info->dev_data;
+
+       /* Stash the timestamp somewhere convenient for the bh */
+       st->last_timestamp = timestamp;
+       schedule_work(&st->work_cont_thresh.ws);
+
+       return 0;
+}
+
+
+/* Unforunately it appears the interrupt won't clear unless you read from the
+ * src register.
+ */
+static void lis3l02dq_thresh_handler_bh_no_check(struct work_struct *work_s)
+{
+       struct iio_work_cont *wc
+               = container_of(work_s, struct iio_work_cont, ws_nocheck);
+       struct lis3l02dq_state *st = wc->st;
+
+       int8_t t;
+
+       lis3l02dq_spi_read_reg_int8_t(st->indio_dev->dev,
+                                     LIS3L02DQ_REG_WAKE_UP_SRC_ADDRESS,
+                                     &t);
+
+       if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH)
+               iio_put_event(st->indio_dev, 0,
+                             IIO_EVENT_CODE_ACCEL_Z_HIGH,
+                             st->last_timestamp);
+
+       if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW)
+               iio_put_event(st->indio_dev, 0,
+                             IIO_EVENT_CODE_ACCEL_Z_LOW,
+                             st->last_timestamp);
+
+       if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH)
+               iio_put_event(st->indio_dev, 0,
+                             IIO_EVENT_CODE_ACCEL_Y_HIGH,
+                             st->last_timestamp);
+
+       if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW)
+               iio_put_event(st->indio_dev, 0,
+                             IIO_EVENT_CODE_ACCEL_Y_LOW,
+                             st->last_timestamp);
+
+       if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH)
+               iio_put_event(st->indio_dev, 0,
+                             IIO_EVENT_CODE_ACCEL_X_HIGH,
+                             st->last_timestamp);
+
+       if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW)
+               iio_put_event(st->indio_dev, 0,
+                             IIO_EVENT_CODE_ACCEL_X_LOW,
+                             st->last_timestamp);
+
+       enable_irq(st->us->irq);
+       /* Ack (and allow for new interrupts? (not clear on data sheet ) )*/
+       lis3l02dq_spi_read_reg_int8_t(st->indio_dev->dev,
+                                     LIS3L02DQ_REG_WAKE_UP_ACK_ADDRESS,
+                                     &t);
+
+       return;
+}
+
+/* This only enables direct output of data ready as an event - not the ring*/
+IIO_EVENT_ATTR_DATA_RDY(lis3l02dq_read_data_ready_config,
+                       lis3l02dq_write_data_ready_config,
+                       0,
+                       &lis3l02dq_data_rdy_event_th);
+
+/* This is an event as it is a response to a physical interrupt */
+IIO_EVENT_SH(sw_ring_enable, &lis3l02dq_data_rdy_ring_th);
+
+static int lis3l02dq_data_rdy_ring_preenable(struct iio_dev *indio_dev)
+{
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       __lis3l02dq_write_data_ready_config(&st->us->dev,
+                                           &iio_event_data_rdy,
+                                           0);
+       return 0;
+}
+
+static int lis3l02dq_data_rdy_ring_postenable(struct iio_dev *indio_dev)
+{
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       __lis3l02dq_write_data_ready_config(&st->us->dev,
+                                           &iio_event_sw_ring_enable,
+                                           1);
+       return 0;
+}
+
+static int lis3l02dq_data_rdy_ring_predisable(struct iio_dev *indio_dev)
+{
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       __lis3l02dq_write_data_ready_config(&st->us->dev,
+                                           &iio_event_sw_ring_enable,
+                                           0);
+       return 0;
+}
+
+/* A shared handler for a number of threshold types */
+IIO_EVENT_SH(threshold, &lis3l02dq_thresh_handler_th);
+
+IIO_EVENT_ATTR_ACCEL_X_HIGH_SH(iio_event_threshold,
+                              lis3l02dq_read_interrupt_config,
+                              lis3l02dq_write_interrupt_config,
+                              LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH);
+
+IIO_EVENT_ATTR_ACCEL_Y_HIGH_SH(iio_event_threshold,
+                              lis3l02dq_read_interrupt_config,
+                              lis3l02dq_write_interrupt_config,
+                              LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH);
+
+IIO_EVENT_ATTR_ACCEL_Z_HIGH_SH(iio_event_threshold,
+                              lis3l02dq_read_interrupt_config,
+                              lis3l02dq_write_interrupt_config,
+                              LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH);
+
+IIO_EVENT_ATTR_ACCEL_X_LOW_SH(iio_event_threshold,
+                             lis3l02dq_read_interrupt_config,
+                             lis3l02dq_write_interrupt_config,
+                             LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_LOW);
+
+IIO_EVENT_ATTR_ACCEL_Y_LOW_SH(iio_event_threshold,
+                             lis3l02dq_read_interrupt_config,
+                             lis3l02dq_write_interrupt_config,
+                             LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW);
+
+IIO_EVENT_ATTR_ACCEL_Z_LOW_SH(iio_event_threshold,
+                             lis3l02dq_read_interrupt_config,
+                             lis3l02dq_write_interrupt_config,
+                             LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW);
+
+static struct attribute *lis3l02dq_event_attributes[] = {
+       &iio_event_attr_x_high.dev_attr.attr,
+       &iio_event_attr_y_high.dev_attr.attr,
+       &iio_event_attr_z_high.dev_attr.attr,
+       &iio_event_attr_x_low.dev_attr.attr,
+       &iio_event_attr_y_low.dev_attr.attr,
+       &iio_event_attr_z_low.dev_attr.attr,
+       &iio_event_attr_data_rdy.dev_attr.attr,
+       NULL
+};
+
+static struct attribute_group lis3l02dq_event_attribute_group = {
+       .attrs = lis3l02dq_event_attributes,
+};
+
+static struct attribute *lis3l02dq_attributes[] = {
+       &iio_dev_attr_x_offset.dev_attr.attr,
+       &iio_dev_attr_y_offset.dev_attr.attr,
+       &iio_dev_attr_z_offset.dev_attr.attr,
+       &iio_dev_attr_x_gain.dev_attr.attr,
+       &iio_dev_attr_y_gain.dev_attr.attr,
+       &iio_dev_attr_z_gain.dev_attr.attr,
+       &iio_dev_attr_thresh.dev_attr.attr,
+       &iio_dev_attr_x.dev_attr.attr,
+       &iio_dev_attr_y.dev_attr.attr,
+       &iio_dev_attr_z.dev_attr.attr,
+       &iio_dev_attr_sampling_frequency.dev_attr.attr,
+       &iio_dev_attr_available_sampling_frequency.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group lis3l02dq_attribute_group = {
+       .attrs = lis3l02dq_attributes,
+};
+
+
+static int __devinit lis3l02dq_probe(struct spi_device *spi)
+{
+       struct lis3l02dq_state *st;
+       int ret;
+       /* must init this somewhere */
+       INIT_LIST_HEAD(&iio_event_sw_ring_enable.list);
+       st = kzalloc(sizeof(struct lis3l02dq_state), GFP_KERNEL);
+       if (st == NULL) {
+               ret =  -ENOMEM;
+               goto error_ret;
+       }
+
+
+       st->us = spi;
+       /* setup the industrialio driver allocated elements */
+       st->indio_dev = kzalloc(sizeof(struct iio_dev), GFP_KERNEL);
+       if (st->indio_dev == NULL) {
+               ret = -ENOMEM;
+               goto error_free_st;
+       }
+
+       st->indio_dev->dev = &spi->dev;
+       st->indio_dev->num_interrupt_lines = 1;
+       st->indio_dev->event_attrs = &lis3l02dq_event_attribute_group;
+       st->indio_dev->attrs = &lis3l02dq_attribute_group;
+       st->indio_dev->dev_data = (void *)(st);
+       /* setup parameters of the ring buffer */
+       st->indio_dev->ring_bytes_per_datum = sizeof(struct lis3l02dq_datum);
+       st->indio_dev->ring_length = 500;
+       st->indio_dev->driver_module = THIS_MODULE;
+       st->indio_dev->modes = INDIO_DIRECT_MODE | INDIO_RING_DATA_RDY;
+       st->indio_dev->ring_preenable = &lis3l02dq_data_rdy_ring_preenable;
+       st->indio_dev->ring_postenable = &lis3l02dq_data_rdy_ring_postenable;
+       st->indio_dev->ring_predisable = &lis3l02dq_data_rdy_ring_predisable;
+
+       ret = iio_device_register(st->indio_dev);
+       if (ret < 0)
+               goto error_free_dev;
+
+       if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) {
+               INIT_WORK(&st->work_data_rdy_ring,
+                         lis3l02dq_data_rdy_bh_to_ring);
+               INIT_WORK(&st->work_data_rdy_event,
+                         lis3l02dq_data_rdy_bh_to_event);
+
+               /* This is a little unusual, in that the device seems
+                  to need a full read of the interrupt source reg before
+                  the interrupt will reset.
+                  Hence the two handlers are the same */
+
+               INIT_IIO_WORK_CONT(&(st->work_cont_thresh),
+                                  lis3l02dq_thresh_handler_bh_no_check,
+                                  lis3l02dq_thresh_handler_bh_no_check,
+                                  LIS3L02DQ_REG_WAKE_UP_SRC_ADDRESS,
+                                  0,
+                                  st);
+               st->inter = 0;
+               ret = iio_register_interrupt_line(spi->irq,
+                                                 st->indio_dev,
+                                                 0,
+                                                 IRQF_TRIGGER_RISING,
+                                                 "lis3l02dq");
+               if (ret)
+                       goto error_unregister_dev;
+       } else
+               st->indio_dev->modes &= ~INDIO_RING_DATA_RDY;
+
+       /* Get the device into a sane initial state */
+       ret = lis3l02dq_initial_setup(st);
+       if (ret)
+               goto error_unregister_line;
+       return 0;
+
+error_unregister_line:
+       if (st->indio_dev->modes & INDIO_RING_DATA_RDY)
+               iio_unregister_interrupt_line(st->indio_dev, 0);
+error_unregister_dev:
+       iio_device_unregister(st->indio_dev);
+error_free_dev:
+       kfree(st->indio_dev);
+error_free_st:
+       kfree(st);
+ error_ret:
+       return ret;
+}
+
+/* Power down the device */
+static int lis3l02dq_stop_device(struct iio_dev *indio_dev)
+{
+       int ret;
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+       mutex_lock(&indio_dev->mlock);
+       ret = lis3l02dq_spi_write_reg_int8_t(&st->us->dev,
+                                            LIS3L02DQ_REG_CTRL_1_ADDRESS,
+                                            0);
+       if (ret) {
+               dev_err(&st->us->dev, "problem with turning device off: ctrl1");
+               goto err_ret;
+       }
+
+       ret = lis3l02dq_spi_write_reg_int8_t(&st->us->dev,
+                                            LIS3L02DQ_REG_CTRL_2_ADDRESS,
+                                            0);
+       if (ret)
+               dev_err(&st->us->dev, "problem with turning device off: ctrl2");
+err_ret:
+       mutex_unlock(&indio_dev->mlock);
+       return ret;
+}
+
+/* fixme, confirm ordering in this function */
+static int lis3l02dq_remove(struct spi_device *spi)
+{
+       int ret;
+       struct iio_dev *indio_dev = spi_get_drvdata(spi);
+       struct lis3l02dq_state *st = indio_dev->dev_data;
+
+       /* stop the device*/
+       /* Amongst other things this must ensure no more interrupts are
+        * generated by the device.
+        */
+       ret = lis3l02dq_stop_device(indio_dev);
+       /* Make sure all bottom halfs of interrupts are done */
+       flush_scheduled_work();
+       if (ret)
+               goto err_ret;
+       /* Fixme slightly misleading test condition - even if valid */
+       if (indio_dev->modes & INDIO_RING_DATA_RDY)
+               iio_unregister_interrupt_line(indio_dev, 0);
+       iio_device_unregister(indio_dev);
+       kfree(indio_dev);
+       kfree(st);
+       return 0;
+
+err_ret:
+       return ret;
+}
+
+static struct spi_driver lis3l02dq_driver = {
+       .driver = {
+               .name = "lis3l02dq",
+               .owner = THIS_MODULE,
+       },
+       .probe = lis3l02dq_probe,
+       .remove = __devexit_p(lis3l02dq_remove),
+};
+
+static __init int lis3l02dq_init(void)
+{
+       return spi_register_driver(&lis3l02dq_driver);
+}
+
+static __exit void lis3l02dq_exit(void)
+{
+       spi_unregister_driver(&lis3l02dq_driver);
+}
+
+module_init(lis3l02dq_init);
+module_exit(lis3l02dq_exit);
+
+MODULE_AUTHOR("Jonathan Cameron <[EMAIL PROTECTED]>");
+MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver");
+MODULE_LICENSE("GPL v2");



-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
spi-devel-general mailing list
spi-devel-general@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/spi-devel-general

Reply via email to