[Patch 3/4] ST LIS3L02DQ accelerometer
From: Jonathan Cameron
Date: Wed Jul 23 2008 - 13:14:31 EST
From: Jonathan Cameron <jic23@xxxxxxxxx>
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 <jic23@xxxxxxxxx>
---
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 <jic23@xxxxxxxxx>
+ *
+ * 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 <jic23@xxxxxxxxx>
+ *
+ * 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 <jic23@xxxxxxxxx>");
+MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver");
+MODULE_LICENSE("GPL v2");
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/