From: Jonathan Cameron <jic23 at cam.ac.uk> 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 at cam.ac.uk> --- 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 at cam.ac.uk> + * + * 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 at cam.ac.uk> + * + * 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 at cam.ac.uk>"); +MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2");